geopyv-dev 0.2.0__tar.gz → 0.2.1__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.
Files changed (157) hide show
  1. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/CLAUDE.md +7 -1
  2. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/Cargo.lock +4 -3
  3. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/Cargo.toml +2 -2
  4. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/PKG-INFO +1 -1
  5. geopyv_dev-0.2.1/geopyv-gui/Cargo.toml +24 -0
  6. geopyv_dev-0.2.1/geopyv-gui/src/app.rs +287 -0
  7. geopyv_dev-0.2.1/geopyv-gui/src/colormap.rs +107 -0
  8. geopyv_dev-0.2.1/geopyv-gui/src/draw.rs +805 -0
  9. geopyv_dev-0.2.1/geopyv-gui/src/field_tab.rs +1533 -0
  10. geopyv_dev-0.2.1/geopyv-gui/src/image_viewer.rs +254 -0
  11. geopyv_dev-0.2.1/geopyv-gui/src/lib.rs +42 -0
  12. geopyv_dev-0.2.1/geopyv-gui/src/main.rs +3 -0
  13. geopyv_dev-0.2.1/geopyv-gui/src/main_window.rs +1213 -0
  14. geopyv_dev-0.2.1/geopyv-gui/src/mesh_tab.rs +1714 -0
  15. geopyv_dev-0.2.1/geopyv-gui/src/particle_tab.rs +896 -0
  16. geopyv_dev-0.2.1/geopyv-gui/src/progress.rs +76 -0
  17. geopyv_dev-0.2.1/geopyv-gui/src/project.rs +294 -0
  18. geopyv_dev-0.2.1/geopyv-gui/src/recent.rs +50 -0
  19. geopyv_dev-0.2.1/geopyv-gui/src/sequence_tab.rs +1720 -0
  20. geopyv_dev-0.2.1/geopyv-gui/src/subset_tab.rs +1668 -0
  21. geopyv_dev-0.2.1/geopyv_dev/gui.py +21 -0
  22. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/pyproject.toml +4 -1
  23. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/python/Cargo.toml +2 -1
  24. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/python/src/lib.rs +2 -0
  25. geopyv_dev-0.2.1/python/src/py_gui.rs +21 -0
  26. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/python/src/py_sequence.rs +1 -1
  27. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/python/src/py_subset.rs +1 -5
  28. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/src/error.rs +4 -0
  29. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/src/field.rs +96 -1
  30. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/src/geometry/utilities.rs +33 -0
  31. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/src/image.rs +3 -0
  32. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/src/lib.rs +1 -0
  33. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/src/masks.rs +21 -0
  34. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/src/mesh.rs +72 -30
  35. geopyv_dev-0.2.1/src/progress.rs +118 -0
  36. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/src/sequence.rs +117 -29
  37. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/.github/workflows/release.yml +0 -0
  38. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/.gitignore +0 -0
  39. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/.readthedocs.yaml +0 -0
  40. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/LICENSE +0 -0
  41. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/Makefile +0 -0
  42. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/README.md +0 -0
  43. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/basictest.py +0 -0
  44. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/_static/gifs/intro_hero.gif +0 -0
  45. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/_static/gifs/quad_deformation.gif +0 -0
  46. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/_static/gifs/region_motion.gif +0 -0
  47. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/_static/gifs/sequence_distortion.gif +0 -0
  48. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/_static/gifs/subset_shape_order.gif +0 -0
  49. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/_static/img/c2_subset.png +0 -0
  50. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/_static/img/combined_25_standard_error.png +0 -0
  51. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/_static/img/mask_local_vs_global.png +0 -0
  52. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/_static/img/mesh_solve_flow.png +0 -0
  53. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/_static/img/sequence_solve_flow.png +0 -0
  54. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/_static/img/subset_quality.png +0 -0
  55. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/api/index.rst +0 -0
  56. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/conf.py +0 -0
  57. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/index.rst +0 -0
  58. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/installation.rst +0 -0
  59. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/requirements-docs.txt +0 -0
  60. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/scripts/build_images_assets.py +0 -0
  61. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/scripts/build_intro_gif.py +0 -0
  62. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/scripts/build_mesh_flow.py +0 -0
  63. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/scripts/build_region_motion_gif.py +0 -0
  64. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/scripts/build_sequence_distortion_gif.py +0 -0
  65. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/scripts/build_sequence_flow.py +0 -0
  66. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/scripts/build_subset_assets.py +0 -0
  67. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/scripts/common.py +0 -0
  68. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/tutorials/00_introduction.rst +0 -0
  69. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/tutorials/01_images_and_masks.rst +0 -0
  70. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/tutorials/02_subset.rst +0 -0
  71. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/tutorials/03_mesh.rst +0 -0
  72. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/tutorials/04_sequence.rst +0 -0
  73. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/tutorials/05_particle.rst +0 -0
  74. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/tutorials/06_field.rst +0 -0
  75. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/tutorials/07_validation.rst +0 -0
  76. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/tutorials/08_calibration.rst +0 -0
  77. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/docs/tutorials/index.rst +0 -0
  78. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/geopyv-dev-tutorials/00_introduction.ipynb +0 -0
  79. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/geopyv-dev-tutorials/01_images_and_masks.ipynb +0 -0
  80. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/geopyv-dev-tutorials/02_subset.ipynb +0 -0
  81. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/geopyv-dev-tutorials/03_mesh.ipynb +0 -0
  82. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/geopyv-dev-tutorials/04_sequence.ipynb +0 -0
  83. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/geopyv-dev-tutorials/05_particle_and_field.ipynb +0 -0
  84. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/geopyv-dev-tutorials/data/sequence/seq_0.jpg +0 -0
  85. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/geopyv-dev-tutorials/data/sequence/seq_1.jpg +0 -0
  86. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/geopyv-dev-tutorials/data/sequence/seq_2.jpg +0 -0
  87. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/geopyv-dev-tutorials/data/sequence/seq_3.jpg +0 -0
  88. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/geopyv-dev-tutorials/data/sequence/seq_4.jpg +0 -0
  89. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/geopyv-dev-tutorials/data/single/intro_0.jpg +0 -0
  90. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/geopyv-dev-tutorials/data/single/intro_1.jpg +0 -0
  91. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/geopyv_dev/__init__.py +0 -0
  92. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/geopyv_dev/calibration.py +0 -0
  93. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/geopyv_dev/plots.py +0 -0
  94. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/geopyv_dev/wrappers.py +0 -0
  95. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/images/SB/SB_0.jpg +0 -0
  96. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/images/SB/SB_1.jpg +0 -0
  97. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/images/SB/SB_2.jpg +0 -0
  98. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/images/SB/SB_3.jpg +0 -0
  99. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/images/SB/SB_4.jpg +0 -0
  100. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/images/SB/SB_5.jpg +0 -0
  101. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/images/SB/speckle_SB.pyv +0 -0
  102. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/images/comp/compression_0.jpg +0 -0
  103. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/images/comp/compression_1.jpg +0 -0
  104. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/images/comp/compression_10.jpg +0 -0
  105. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/images/comp/compression_2.jpg +0 -0
  106. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/images/comp/compression_3.jpg +0 -0
  107. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/images/comp/compression_4.jpg +0 -0
  108. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/images/comp/compression_5.jpg +0 -0
  109. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/images/comp/compression_6.jpg +0 -0
  110. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/images/comp/compression_7.jpg +0 -0
  111. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/images/comp/compression_8.jpg +0 -0
  112. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/images/comp/compression_9.jpg +0 -0
  113. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/images/quality/quality_speckle.jpg +0 -0
  114. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/images/shear/shear_0.jpg +0 -0
  115. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/images/shear/shear_1.jpg +0 -0
  116. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/images/shear/shear_2.jpg +0 -0
  117. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/images/shear/shear_3.jpg +0 -0
  118. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/images/shear/shear_4.jpg +0 -0
  119. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/images/shear/speckle_shear.pyv +0 -0
  120. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/python/geopyv-dev/__init__.py +0 -0
  121. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/python/src/py_calibration.rs +0 -0
  122. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/python/src/py_field.rs +0 -0
  123. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/python/src/py_geometry.rs +0 -0
  124. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/python/src/py_image.rs +0 -0
  125. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/python/src/py_io.rs +0 -0
  126. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/python/src/py_mask.rs +0 -0
  127. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/python/src/py_mesh.rs +0 -0
  128. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/python/src/py_particle.rs +0 -0
  129. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/python/src/py_speckle.rs +0 -0
  130. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/python/src/py_validation.rs +0 -0
  131. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/python/src/utils.rs +0 -0
  132. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/src/calibration.rs +0 -0
  133. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/src/geometry/meshing.rs +0 -0
  134. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/src/geometry/mod.rs +0 -0
  135. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/src/geometry/rasterize.rs +0 -0
  136. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/src/geometry/region.rs +0 -0
  137. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/src/geometry/triangulation.rs +0 -0
  138. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/src/io.rs +0 -0
  139. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/src/particle.rs +0 -0
  140. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/src/speckle.rs +0 -0
  141. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/src/subset.rs +0 -0
  142. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/src/validation.rs +0 -0
  143. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/tests/python/calibration/test_calibration.py +0 -0
  144. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/tests/python/field/test_field.py +0 -0
  145. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/tests/python/geometry/test_geometry.py +0 -0
  146. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/tests/python/image/test_image.py +0 -0
  147. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/tests/python/io/test_io.py +0 -0
  148. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/tests/python/mesh/test_mesh.py +0 -0
  149. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/tests/python/particle/test_particle.py +0 -0
  150. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/tests/python/plots/__init__.py +0 -0
  151. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/tests/python/plots/conftest.py +0 -0
  152. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/tests/python/plots/test_plots.py +0 -0
  153. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/tests/python/sequence/test_sequence.py +0 -0
  154. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/tests/python/subset/test_subset.py +0 -0
  155. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/tests/python/templates/test_templates.py +0 -0
  156. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/tests/python/validation/test_validation.py +0 -0
  157. {geopyv_dev-0.2.0 → geopyv_dev-0.2.1}/tests/python/validation/test_validation_solve.py +0 -0
@@ -34,13 +34,19 @@ pytest tests/python/mesh/test_mesh.py
34
34
  source .venv/bin/activate && python basictest.py
35
35
  ```
36
36
 
37
- ### Build the GUI (separate binary crate)
37
+ ### Build the GUI
38
38
 
39
39
  ```bash
40
40
  cargo build -p geopyv-gui
41
41
  cargo run -p geopyv-gui
42
42
  ```
43
43
 
44
+ `geopyv-gui` is a library (`geopyv_gui::run`) plus a thin binary. The Python
45
+ extension links it and exposes `run_gui`; `pyproject.toml` installs a
46
+ `geopyv-gui` console script (`geopyv_dev/gui.py`), so after `maturin develop`
47
+ or `pip install geopyv-dev` users just run `geopyv-gui`. The GUI is a thin
48
+ front-end: solving, defaults and derived quantities belong in the core.
49
+
44
50
  ### Publishing a release to PyPI
45
51
 
46
52
  The `.github/workflows/release.yml` workflow triggers **only on version tags** — pushing a branch does nothing. Steps:
@@ -1531,7 +1531,7 @@ dependencies = [
1531
1531
 
1532
1532
  [[package]]
1533
1533
  name = "geopyv-dev"
1534
- version = "0.2.0"
1534
+ version = "0.2.1"
1535
1535
  dependencies = [
1536
1536
  "bincode",
1537
1537
  "image",
@@ -1550,9 +1550,10 @@ dependencies = [
1550
1550
 
1551
1551
  [[package]]
1552
1552
  name = "geopyv-dev-python"
1553
- version = "0.2.0"
1553
+ version = "0.2.1"
1554
1554
  dependencies = [
1555
1555
  "geopyv-dev",
1556
+ "geopyv-gui",
1556
1557
  "ndarray",
1557
1558
  "numpy",
1558
1559
  "pyo3",
@@ -1560,7 +1561,7 @@ dependencies = [
1560
1561
 
1561
1562
  [[package]]
1562
1563
  name = "geopyv-gui"
1563
- version = "0.2.0"
1564
+ version = "0.2.1"
1564
1565
  dependencies = [
1565
1566
  "dirs",
1566
1567
  "eframe",
@@ -1,10 +1,10 @@
1
1
  [workspace]
2
- members = [".", "python"]
2
+ members = [".", "python", "geopyv-gui"]
3
3
  resolver = "2"
4
4
 
5
5
  [package]
6
6
  name = "geopyv-dev"
7
- version = "0.2.0"
7
+ version = "0.2.1"
8
8
  edition = "2024"
9
9
 
10
10
  [dependencies]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: geopyv_dev
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Rust
6
6
  Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
@@ -0,0 +1,24 @@
1
+ [package]
2
+ name = "geopyv-gui"
3
+ version = "0.2.1"
4
+ edition = "2024"
5
+
6
+ [lib]
7
+ name = "geopyv_gui"
8
+ path = "src/lib.rs"
9
+
10
+ [[bin]]
11
+ name = "geopyv-gui"
12
+ path = "src/main.rs"
13
+
14
+ [dependencies]
15
+ geopyv-dev = { path = ".." }
16
+ eframe = { version = "0.31", features = ["default"] }
17
+ egui = "0.31"
18
+ egui_plot = "0.31"
19
+ rfd = "0.15"
20
+ serde = { version = "1", features = ["derive"] }
21
+ serde_json = "1"
22
+ dirs = "6"
23
+ image = { version = "0.25", default-features = false, features = ["jpeg", "png", "tiff"] }
24
+ ndarray = "0.16"
@@ -0,0 +1,287 @@
1
+ use eframe::egui;
2
+
3
+ use crate::main_window::MainWindow;
4
+ use crate::project::Project;
5
+ use crate::recent::RecentProjects;
6
+
7
+ // ---------------------------------------------------------------------------
8
+ // Top-level application state
9
+ // ---------------------------------------------------------------------------
10
+
11
+ pub struct GeopyvApp {
12
+ state: AppState,
13
+ recent: RecentProjects,
14
+ error: Option<String>,
15
+ }
16
+
17
+ enum AppState {
18
+ Startup,
19
+ ProjectOpen {
20
+ project: Project,
21
+ window: MainWindow,
22
+ },
23
+ }
24
+
25
+ impl GeopyvApp {
26
+ pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
27
+ configure_visuals(&cc.egui_ctx);
28
+ Self {
29
+ state: AppState::Startup,
30
+ recent: RecentProjects::load(),
31
+ error: None,
32
+ }
33
+ }
34
+
35
+ // -----------------------------------------------------------------------
36
+ // Startup window
37
+ // -----------------------------------------------------------------------
38
+
39
+ fn show_startup(&mut self, ctx: &egui::Context) {
40
+ // Constrain the startup window to a compact size.
41
+ ctx.send_viewport_cmd(egui::ViewportCommand::MinInnerSize(egui::vec2(400.0, 260.0)));
42
+ ctx.send_viewport_cmd(egui::ViewportCommand::MaxInnerSize(egui::vec2(560.0, 600.0)));
43
+
44
+ egui::CentralPanel::default().show(ctx, |ui| {
45
+ ui.vertical_centered(|ui| {
46
+ ui.add_space(40.0);
47
+
48
+ ui.heading(egui::RichText::new("geopyv").size(32.0).strong());
49
+ ui.add_space(4.0);
50
+ ui.label(
51
+ egui::RichText::new("Digital Image Correlation")
52
+ .size(16.0)
53
+ .color(ui.visuals().weak_text_color()),
54
+ );
55
+
56
+ ui.add_space(32.0);
57
+
58
+ let btn_size = egui::vec2(180.0, 36.0);
59
+
60
+ if ui
61
+ .add_sized(btn_size, egui::Button::new("New Project"))
62
+ .clicked()
63
+ {
64
+ self.action_new_project();
65
+ }
66
+
67
+ ui.add_space(8.0);
68
+
69
+ if ui
70
+ .add_sized(btn_size, egui::Button::new("Open Project"))
71
+ .clicked()
72
+ {
73
+ self.action_open_project();
74
+ }
75
+
76
+ self.show_recent_list(ui);
77
+ });
78
+ });
79
+ }
80
+
81
+ fn show_recent_list(&mut self, ui: &mut egui::Ui) {
82
+ if self.recent.paths.is_empty() {
83
+ return;
84
+ }
85
+
86
+ ui.add_space(28.0);
87
+ ui.separator();
88
+ ui.add_space(8.0);
89
+ ui.label(
90
+ egui::RichText::new("Recent")
91
+ .size(14.0)
92
+ .color(ui.visuals().weak_text_color()),
93
+ );
94
+ ui.add_space(4.0);
95
+
96
+ let mut open_path: Option<std::path::PathBuf> = None;
97
+ let mut remove_path: Option<std::path::PathBuf> = None;
98
+
99
+ for path in &self.recent.paths {
100
+ let label = path
101
+ .file_name()
102
+ .map(|n| n.to_string_lossy().into_owned())
103
+ .unwrap_or_else(|| path.to_string_lossy().into_owned());
104
+
105
+ let exists = path.is_dir() && path.join("project.json").exists();
106
+
107
+ ui.horizontal(|ui| {
108
+ let resp = ui.add(
109
+ egui::Button::new(
110
+ egui::RichText::new(&label).size(15.0).color(if exists {
111
+ ui.visuals().text_color()
112
+ } else {
113
+ ui.visuals().weak_text_color()
114
+ }),
115
+ )
116
+ .frame(false),
117
+ );
118
+
119
+ if resp.clicked() {
120
+ if exists {
121
+ open_path = Some(path.clone());
122
+ } else {
123
+ remove_path = Some(path.clone());
124
+ }
125
+ }
126
+
127
+ resp.on_hover_text(path.display().to_string());
128
+
129
+ if !exists {
130
+ ui.label(
131
+ egui::RichText::new("(missing)")
132
+ .size(13.0)
133
+ .color(ui.visuals().error_fg_color),
134
+ );
135
+ }
136
+ });
137
+ }
138
+
139
+ if let Some(p) = open_path {
140
+ self.open_project_at(p);
141
+ }
142
+ if let Some(p) = remove_path {
143
+ self.recent.remove(&p);
144
+ }
145
+ }
146
+
147
+ // -----------------------------------------------------------------------
148
+ // Project open / create actions
149
+ // -----------------------------------------------------------------------
150
+
151
+ fn action_new_project(&mut self) {
152
+ let Some(folder) = rfd::FileDialog::new()
153
+ .set_title("Choose location for new project")
154
+ .pick_folder()
155
+ else {
156
+ return;
157
+ };
158
+
159
+ match Project::create(&folder) {
160
+ Ok(project) => self.load_project(project),
161
+ Err(e) => self.error = Some(format!("Could not create project: {e}")),
162
+ }
163
+ }
164
+
165
+ fn action_open_project(&mut self) {
166
+ let Some(folder) = rfd::FileDialog::new()
167
+ .set_title("Open project folder")
168
+ .pick_folder()
169
+ else {
170
+ return;
171
+ };
172
+
173
+ self.open_project_at(folder);
174
+ }
175
+
176
+ fn open_project_at(&mut self, path: std::path::PathBuf) {
177
+ match Project::open(&path) {
178
+ Ok(project) => self.load_project(project),
179
+ Err(e) => self.error = Some(format!("Could not open project: {e}")),
180
+ }
181
+ }
182
+
183
+ fn load_project(&mut self, project: Project) {
184
+ self.recent.push(&project.root);
185
+ // Restore the main window to full size.
186
+ // (MaxInnerSize from startup mode must be lifted.)
187
+ self.state = AppState::ProjectOpen {
188
+ project,
189
+ window: MainWindow::new(),
190
+ };
191
+ }
192
+
193
+ // -----------------------------------------------------------------------
194
+ // Main window
195
+ // -----------------------------------------------------------------------
196
+
197
+ fn show_main(&mut self, ctx: &egui::Context) {
198
+ // Lift the startup size constraints.
199
+ ctx.send_viewport_cmd(egui::ViewportCommand::MinInnerSize(egui::vec2(800.0, 500.0)));
200
+ ctx.send_viewport_cmd(egui::ViewportCommand::MaxInnerSize(egui::vec2(f32::INFINITY, f32::INFINITY)));
201
+
202
+ let AppState::ProjectOpen { project, window } = &mut self.state else {
203
+ return;
204
+ };
205
+
206
+ ctx.send_viewport_cmd(egui::ViewportCommand::Title(
207
+ format!("geopyv \u{2014} {}", project.meta.name),
208
+ ));
209
+
210
+ window.show(ctx, project);
211
+ }
212
+
213
+ // -----------------------------------------------------------------------
214
+ // Error modal
215
+ // -----------------------------------------------------------------------
216
+
217
+ fn show_error_modal(&mut self, ctx: &egui::Context) {
218
+ let Some(msg) = self.error.clone() else {
219
+ return;
220
+ };
221
+
222
+ let mut open = true;
223
+ egui::Window::new("Error")
224
+ .collapsible(false)
225
+ .resizable(false)
226
+ .anchor(egui::Align2::CENTER_CENTER, egui::vec2(0.0, 0.0))
227
+ .open(&mut open)
228
+ .show(ctx, |ui| {
229
+ ui.label(&msg);
230
+ ui.add_space(8.0);
231
+ if ui.button("OK").clicked() {
232
+ self.error = None;
233
+ }
234
+ });
235
+
236
+ if !open {
237
+ self.error = None;
238
+ }
239
+ }
240
+ }
241
+
242
+ impl eframe::App for GeopyvApp {
243
+ fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
244
+ match &self.state {
245
+ AppState::Startup => self.show_startup(ctx),
246
+ AppState::ProjectOpen { .. } => self.show_main(ctx),
247
+ }
248
+
249
+ self.show_error_modal(ctx);
250
+ }
251
+ }
252
+
253
+ // ---------------------------------------------------------------------------
254
+ // Theme
255
+ // ---------------------------------------------------------------------------
256
+
257
+ fn configure_visuals(ctx: &egui::Context) {
258
+ let mut visuals = egui::Visuals::dark();
259
+
260
+ visuals.window_fill = egui::Color32::from_rgb(22, 22, 24);
261
+ visuals.panel_fill = egui::Color32::from_rgb(22, 22, 24);
262
+ visuals.faint_bg_color = egui::Color32::from_rgb(30, 30, 32);
263
+
264
+ visuals.window_corner_radius = egui::CornerRadius::same(6);
265
+ visuals.menu_corner_radius = egui::CornerRadius::same(4);
266
+
267
+ visuals.widgets.inactive.bg_fill = egui::Color32::from_rgb(42, 42, 46);
268
+ visuals.widgets.hovered.bg_fill = egui::Color32::from_rgb(58, 58, 64);
269
+ visuals.widgets.active.bg_fill = egui::Color32::from_rgb(70, 70, 80);
270
+
271
+ ctx.set_visuals(visuals);
272
+
273
+ let mut style = (*ctx.style()).clone();
274
+ style.text_styles.insert(
275
+ egui::TextStyle::Body,
276
+ egui::FontId::new(16.0, egui::FontFamily::Proportional),
277
+ );
278
+ style.text_styles.insert(
279
+ egui::TextStyle::Button,
280
+ egui::FontId::new(16.0, egui::FontFamily::Proportional),
281
+ );
282
+ style.text_styles.insert(
283
+ egui::TextStyle::Heading,
284
+ egui::FontId::new(22.0, egui::FontFamily::Proportional),
285
+ );
286
+ ctx.set_style(style);
287
+ }
@@ -0,0 +1,107 @@
1
+ use eframe::egui;
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // Colormap type
5
+ // ---------------------------------------------------------------------------
6
+
7
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
8
+ pub enum ColormapType {
9
+ #[default]
10
+ Viridis,
11
+ Plasma,
12
+ Inferno,
13
+ }
14
+
15
+ impl ColormapType {
16
+ pub const ALL: &'static [ColormapType] = &[
17
+ ColormapType::Viridis,
18
+ ColormapType::Plasma,
19
+ ColormapType::Inferno,
20
+ ];
21
+
22
+ pub fn label(self) -> &'static str {
23
+ match self {
24
+ ColormapType::Viridis => "Viridis",
25
+ ColormapType::Plasma => "Plasma",
26
+ ColormapType::Inferno => "Inferno",
27
+ }
28
+ }
29
+ }
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // 9-keyframe LUTs (t = 0, 0.125, 0.25, ..., 1.0)
33
+ // Values are (R, G, B) in [0, 255].
34
+ // ---------------------------------------------------------------------------
35
+
36
+ static VIRIDIS: [[u8; 3]; 9] = [
37
+ [68, 1, 84],
38
+ [71, 44, 122],
39
+ [59, 82, 139],
40
+ [44, 113, 142],
41
+ [32, 144, 140],
42
+ [35, 168, 132],
43
+ [93, 201, 99],
44
+ [161, 218, 62],
45
+ [253, 231, 37],
46
+ ];
47
+
48
+ static PLASMA: [[u8; 3]; 9] = [
49
+ [ 13, 8, 135],
50
+ [ 84, 2, 163],
51
+ [139, 10, 165],
52
+ [185, 50, 137],
53
+ [219, 92, 104],
54
+ [244, 136, 73],
55
+ [253, 183, 45],
56
+ [249, 230, 51],
57
+ [240, 249, 33],
58
+ ];
59
+
60
+ static INFERNO: [[u8; 3]; 9] = [
61
+ [ 0, 0, 4],
62
+ [ 40, 11, 84],
63
+ [101, 21, 110],
64
+ [159, 42, 99],
65
+ [212, 72, 66],
66
+ [245, 125, 21],
67
+ [252, 185, 20],
68
+ [253, 236, 98],
69
+ [252, 255, 164],
70
+ ];
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // Public API
74
+ // ---------------------------------------------------------------------------
75
+
76
+ /// Map `t ∈ [0, 1]` to an opaque colour via the chosen colormap.
77
+ pub fn sample(t: f32, cmap: ColormapType) -> egui::Color32 {
78
+ let lut: &[[u8; 3]; 9] = match cmap {
79
+ ColormapType::Viridis => &VIRIDIS,
80
+ ColormapType::Plasma => &PLASMA,
81
+ ColormapType::Inferno => &INFERNO,
82
+ };
83
+
84
+ let t = t.clamp(0.0, 1.0);
85
+ let scaled = t * 8.0; // 9 keyframes → 8 segments
86
+ let lo = (scaled as usize).min(7);
87
+ let hi = lo + 1;
88
+ let frac = scaled - lo as f32;
89
+
90
+ let r = lerp_u8(lut[lo][0], lut[hi][0], frac);
91
+ let g = lerp_u8(lut[lo][1], lut[hi][1], frac);
92
+ let b = lerp_u8(lut[lo][2], lut[hi][2], frac);
93
+ egui::Color32::from_rgb(r, g, b)
94
+ }
95
+
96
+ /// Map a scalar value to a colour given a [vmin, vmax] range.
97
+ pub fn map_value(v: f64, vmin: f64, vmax: f64, cmap: ColormapType) -> egui::Color32 {
98
+ if vmax <= vmin {
99
+ return sample(0.5, cmap);
100
+ }
101
+ let t = ((v - vmin) / (vmax - vmin)) as f32;
102
+ sample(t, cmap)
103
+ }
104
+
105
+ fn lerp_u8(a: u8, b: u8, t: f32) -> u8 {
106
+ (a as f32 + (b as f32 - a as f32) * t).round() as u8
107
+ }