Dropdowns: menus grow back after a search with no matches (#4337)

* chore(deps): pin craft-libs to 62bb0e1d (shared combo box)

* fix(ui-egui): dropdown menus grow back after their contents shrink

Every combo box is craft_ui::ComboBox, whose menu follows its contents'
height while it is open (egui#5225); clippy.toml bans egui::ComboBox.

The font family menu is a craft_ui::combo::Menu under its field, with the
search field and filters in its header and the family rows scrolling under
them, still laid out only in view. The menu keeps to the room on its side
of the field, so the list's minimum height and the popup's room are gone.

* fix(ui-egui): the shortcut import's target picker grows back after a search

Its search field, "No equivalent" and the row's choices are the header of
a craft_ui::combo::Menu, and Keyboard Shortcuts' entries scroll under
them. That was the last menu on the popup path that capped itself at the
room left, so that path and combo's `searchable` flag are gone.

* chore(deps): craft-libs 60d60d48 (dropdown fixes)

* chore(deps): craft-libs 4d7b82f7 (dropdown fixes)

* chore(deps): craft-libs 92d392cf (#387 merged)

* chore(xtask): pdf-deps expects craft-libs 92d392cf
This commit is contained in:
aiongg
2026-10-27 10:36:58 -04:00
committed by GitHub
parent 443b31b13d
commit 13d837363d
25 changed files with 583 additions and 263 deletions
+17 -17
View File
@@ -352,20 +352,20 @@ The shared PDF backend keeps the existing standard-font and CMap features. These
| Crate / file | Author | Source | Licence | SHA-256 | Use |
| --- | --- | --- | --- | --- | --- |
| `hayro-interpret 0.7.0 / assets/FoxitDingbats.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/craft-libs/blob/a0916808f7d78bed8c7b90cdf937176015454a7f/vendor/hayro-interpret/assets/FoxitDingbats.pfb | BSD-3-Clause | `845c752392b6c914fb989c75a08b7792b88f542d2499042ef2889f8c814a16ed` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitFixed.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/craft-libs/blob/a0916808f7d78bed8c7b90cdf937176015454a7f/vendor/hayro-interpret/assets/FoxitFixed.pfb | BSD-3-Clause | `b6c8fe53f134b8b6d4578cd2d544df4cee9624c4efa8d51a560fb40ea296101b` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitFixedBold.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/craft-libs/blob/a0916808f7d78bed8c7b90cdf937176015454a7f/vendor/hayro-interpret/assets/FoxitFixedBold.pfb | BSD-3-Clause | `f1b7159702973f54fc86254ea38bcf3712b2a736eb3a0995751e9f4bb45ad603` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitFixedBoldItalic.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/craft-libs/blob/a0916808f7d78bed8c7b90cdf937176015454a7f/vendor/hayro-interpret/assets/FoxitFixedBoldItalic.pfb | BSD-3-Clause | `8a000945843bd31add06aee63bf9fd41b7578b4f3242a4b7cd46349ad9d24d4d` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitFixedItalic.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/craft-libs/blob/a0916808f7d78bed8c7b90cdf937176015454a7f/vendor/hayro-interpret/assets/FoxitFixedItalic.pfb | BSD-3-Clause | `5007faf8320fa1fcc08b8894b356ad976f60b992ba29ee5272578bbdebaf3876` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitSans.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/craft-libs/blob/a0916808f7d78bed8c7b90cdf937176015454a7f/vendor/hayro-interpret/assets/FoxitSans.pfb | BSD-3-Clause | `a02339e95d20db14cbdfc1c6baf359cd36a091d5434925c3a4040f60e1a1af9e` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitSansBold.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/craft-libs/blob/a0916808f7d78bed8c7b90cdf937176015454a7f/vendor/hayro-interpret/assets/FoxitSansBold.pfb | BSD-3-Clause | `7bdfa36de1d5757dcfaf8b8a4be95c6e41c40fa70dbed9d78bf979a7b9959054` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitSansBoldItalic.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/craft-libs/blob/a0916808f7d78bed8c7b90cdf937176015454a7f/vendor/hayro-interpret/assets/FoxitSansBoldItalic.pfb | BSD-3-Clause | `555ce45924338e2e4b32821910221ce85da691fd59a9723c4c971a7c5627fc8b` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitSansItalic.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/craft-libs/blob/a0916808f7d78bed8c7b90cdf937176015454a7f/vendor/hayro-interpret/assets/FoxitSansItalic.pfb | BSD-3-Clause | `7751cbefeda351708dae8c4a88db043411bc39ab2ae2097685da845d4d16f307` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitSerif.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/craft-libs/blob/a0916808f7d78bed8c7b90cdf937176015454a7f/vendor/hayro-interpret/assets/FoxitSerif.pfb | BSD-3-Clause | `4f57d2b9d884af8f907bf22df6019b52d86cbf6214fdbd02b1ae05472a543f35` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitSerifBold.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/craft-libs/blob/a0916808f7d78bed8c7b90cdf937176015454a7f/vendor/hayro-interpret/assets/FoxitSerifBold.pfb | BSD-3-Clause | `0bdf4b04e964139818d51eda03d566ba999fc3ba2421b1c6c51f9dc969022e80` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitSerifBoldItalic.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/craft-libs/blob/a0916808f7d78bed8c7b90cdf937176015454a7f/vendor/hayro-interpret/assets/FoxitSerifBoldItalic.pfb | BSD-3-Clause | `a406cac82583bf98175cb62c87ed5e95c45fbb34eece63a10b0a13e793cb2e10` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitSerifItalic.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/craft-libs/blob/a0916808f7d78bed8c7b90cdf937176015454a7f/vendor/hayro-interpret/assets/FoxitSerifItalic.pfb | BSD-3-Clause | `610ae0687198045c4db5d1a6650fd5e92536631706382eb8b72e38df578d0ae9` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitSymbol.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/craft-libs/blob/a0916808f7d78bed8c7b90cdf937176015454a7f/vendor/hayro-interpret/assets/FoxitSymbol.pfb | BSD-3-Clause | `47967d055530e7357088a08403115425643ec2cdfd6201ba8af0fbd7116c1539` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/CGATS001Compat-v2-micro.icc` | Clinton Ingram (saucecontrol/Compact-ICC-Profiles) | https://github.com/storytold/craft-libs/blob/a0916808f7d78bed8c7b90cdf937176015454a7f/vendor/hayro-interpret/assets/CGATS001Compat-v2-micro.icc | CC0-1.0 | `73e1ba37d2bad5bab2a964f40a9eed96209666efc067c3322626214bbef234a0` | Default CMYK colour conversion (pinned hayro-interpret) |
| `hayro-cmap 0.1.0 / assets/cmaps.brotli` | Adobe | https://github.com/storytold/craft-libs/blob/a0916808f7d78bed8c7b90cdf937176015454a7f/vendor/hayro-cmap/assets/cmaps.brotli | BSD-3-Clause | `ddf37af521d904d5646c2fc37b2b7e912dc1b56f0d210190b61f598154758e46` | Character-code mapping tables required to read CJK PDFs. Non-visual dependency data; existing embedding unchanged |
| `hayro-interpret 0.7.0 / src/font/generated/metrics.rs` | Adobe (Core 14 AFM metrics, PDF specification encodings); tables by The Hayro Authors | https://github.com/storytold/craft-libs/blob/a0916808f7d78bed8c7b90cdf937176015454a7f/vendor/hayro-interpret/src/font/generated/metrics.rs | MIT OR Apache-2.0 | `2ec9e608573eda459de1e1d9bda3bb494315e36bee885acc88912a11fe0caa41` | Glyph widths and encodings for non-embedded standard fonts. Non-visual dependency data; existing embedding unchanged |
| `hayro-interpret 0.7.0 / assets/FoxitDingbats.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/craft-libs/blob/92d392cff132eaf53e13afff77f38b5c11b73aa0/vendor/hayro-interpret/assets/FoxitDingbats.pfb | BSD-3-Clause | `845c752392b6c914fb989c75a08b7792b88f542d2499042ef2889f8c814a16ed` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitFixed.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/craft-libs/blob/92d392cff132eaf53e13afff77f38b5c11b73aa0/vendor/hayro-interpret/assets/FoxitFixed.pfb | BSD-3-Clause | `b6c8fe53f134b8b6d4578cd2d544df4cee9624c4efa8d51a560fb40ea296101b` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitFixedBold.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/craft-libs/blob/92d392cff132eaf53e13afff77f38b5c11b73aa0/vendor/hayro-interpret/assets/FoxitFixedBold.pfb | BSD-3-Clause | `f1b7159702973f54fc86254ea38bcf3712b2a736eb3a0995751e9f4bb45ad603` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitFixedBoldItalic.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/craft-libs/blob/92d392cff132eaf53e13afff77f38b5c11b73aa0/vendor/hayro-interpret/assets/FoxitFixedBoldItalic.pfb | BSD-3-Clause | `8a000945843bd31add06aee63bf9fd41b7578b4f3242a4b7cd46349ad9d24d4d` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitFixedItalic.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/craft-libs/blob/92d392cff132eaf53e13afff77f38b5c11b73aa0/vendor/hayro-interpret/assets/FoxitFixedItalic.pfb | BSD-3-Clause | `5007faf8320fa1fcc08b8894b356ad976f60b992ba29ee5272578bbdebaf3876` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitSans.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/craft-libs/blob/92d392cff132eaf53e13afff77f38b5c11b73aa0/vendor/hayro-interpret/assets/FoxitSans.pfb | BSD-3-Clause | `a02339e95d20db14cbdfc1c6baf359cd36a091d5434925c3a4040f60e1a1af9e` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitSansBold.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/craft-libs/blob/92d392cff132eaf53e13afff77f38b5c11b73aa0/vendor/hayro-interpret/assets/FoxitSansBold.pfb | BSD-3-Clause | `7bdfa36de1d5757dcfaf8b8a4be95c6e41c40fa70dbed9d78bf979a7b9959054` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitSansBoldItalic.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/craft-libs/blob/92d392cff132eaf53e13afff77f38b5c11b73aa0/vendor/hayro-interpret/assets/FoxitSansBoldItalic.pfb | BSD-3-Clause | `555ce45924338e2e4b32821910221ce85da691fd59a9723c4c971a7c5627fc8b` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitSansItalic.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/craft-libs/blob/92d392cff132eaf53e13afff77f38b5c11b73aa0/vendor/hayro-interpret/assets/FoxitSansItalic.pfb | BSD-3-Clause | `7751cbefeda351708dae8c4a88db043411bc39ab2ae2097685da845d4d16f307` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitSerif.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/craft-libs/blob/92d392cff132eaf53e13afff77f38b5c11b73aa0/vendor/hayro-interpret/assets/FoxitSerif.pfb | BSD-3-Clause | `4f57d2b9d884af8f907bf22df6019b52d86cbf6214fdbd02b1ae05472a543f35` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitSerifBold.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/craft-libs/blob/92d392cff132eaf53e13afff77f38b5c11b73aa0/vendor/hayro-interpret/assets/FoxitSerifBold.pfb | BSD-3-Clause | `0bdf4b04e964139818d51eda03d566ba999fc3ba2421b1c6c51f9dc969022e80` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitSerifBoldItalic.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/craft-libs/blob/92d392cff132eaf53e13afff77f38b5c11b73aa0/vendor/hayro-interpret/assets/FoxitSerifBoldItalic.pfb | BSD-3-Clause | `a406cac82583bf98175cb62c87ed5e95c45fbb34eece63a10b0a13e793cb2e10` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitSerifItalic.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/craft-libs/blob/92d392cff132eaf53e13afff77f38b5c11b73aa0/vendor/hayro-interpret/assets/FoxitSerifItalic.pfb | BSD-3-Clause | `610ae0687198045c4db5d1a6650fd5e92536631706382eb8b72e38df578d0ae9` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitSymbol.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/craft-libs/blob/92d392cff132eaf53e13afff77f38b5c11b73aa0/vendor/hayro-interpret/assets/FoxitSymbol.pfb | BSD-3-Clause | `47967d055530e7357088a08403115425643ec2cdfd6201ba8af0fbd7116c1539` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/CGATS001Compat-v2-micro.icc` | Clinton Ingram (saucecontrol/Compact-ICC-Profiles) | https://github.com/storytold/craft-libs/blob/92d392cff132eaf53e13afff77f38b5c11b73aa0/vendor/hayro-interpret/assets/CGATS001Compat-v2-micro.icc | CC0-1.0 | `73e1ba37d2bad5bab2a964f40a9eed96209666efc067c3322626214bbef234a0` | Default CMYK colour conversion (pinned hayro-interpret) |
| `hayro-cmap 0.1.0 / assets/cmaps.brotli` | Adobe | https://github.com/storytold/craft-libs/blob/92d392cff132eaf53e13afff77f38b5c11b73aa0/vendor/hayro-cmap/assets/cmaps.brotli | BSD-3-Clause | `ddf37af521d904d5646c2fc37b2b7e912dc1b56f0d210190b61f598154758e46` | Character-code mapping tables required to read CJK PDFs. Non-visual dependency data; existing embedding unchanged |
| `hayro-interpret 0.7.0 / src/font/generated/metrics.rs` | Adobe (Core 14 AFM metrics, PDF specification encodings); tables by The Hayro Authors | https://github.com/storytold/craft-libs/blob/92d392cff132eaf53e13afff77f38b5c11b73aa0/vendor/hayro-interpret/src/font/generated/metrics.rs | MIT OR Apache-2.0 | `2ec9e608573eda459de1e1d9bda3bb494315e36bee885acc88912a11fe0caa41` | Glyph widths and encodings for non-embedded standard fonts. Non-visual dependency data; existing embedding unchanged |
Generated
+184 -15
View File
@@ -803,6 +803,15 @@ dependencies = [
"x509-cert",
]
[[package]]
name = "cobs"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
dependencies = [
"thiserror 2.0.21",
]
[[package]]
name = "codespan-reporting"
version = "0.13.1"
@@ -950,12 +959,40 @@ dependencies = [
[[package]]
name = "craft-docpath"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=a0916808f7d78bed8c7b90cdf937176015454a7f#a0916808f7d78bed8c7b90cdf937176015454a7f"
source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0"
[[package]]
name = "craft-fonts-build"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0"
[[package]]
name = "craft-i18n"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0"
dependencies = [
"fixed_decimal",
"icu_decimal",
"icu_locale_core",
"icu_locale_fallback",
"icu_normalizer",
"icu_plurals",
"icu_properties",
"icu_provider",
"icu_provider_adapters",
"icu_provider_blob",
"log",
"objc2-foundation 0.3.2",
"sys-locale",
"unicode-bidi",
"windows-registry",
"writeable",
]
[[package]]
name = "craft-keymap"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=a0916808f7d78bed8c7b90cdf937176015454a7f#a0916808f7d78bed8c7b90cdf937176015454a7f"
source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0"
dependencies = [
"egui",
"serde",
@@ -965,7 +1002,7 @@ dependencies = [
[[package]]
name = "craft-pdf"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=a0916808f7d78bed8c7b90cdf937176015454a7f#a0916808f7d78bed8c7b90cdf937176015454a7f"
source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0"
dependencies = [
"hayro-interpret",
"hayro-syntax",
@@ -976,11 +1013,11 @@ dependencies = [
[[package]]
name = "craft-print"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=a0916808f7d78bed8c7b90cdf937176015454a7f#a0916808f7d78bed8c7b90cdf937176015454a7f"
source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0"
dependencies = [
"craft-winprint-gdi",
"miniz_oxide 0.8.9",
"roxmltree 0.20.0",
"miniz_oxide 0.9.1",
"roxmltree 0.21.1",
"tempfile",
"winprint",
"winsafe",
@@ -989,20 +1026,28 @@ dependencies = [
[[package]]
name = "craft-ui"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=a0916808f7d78bed8c7b90cdf937176015454a7f#a0916808f7d78bed8c7b90cdf937176015454a7f"
source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0"
dependencies = [
"craft-fonts-build",
"craft-i18n",
"egui",
"epaint",
"js-sys",
"log",
"objc2 0.6.5",
"objc2-app-kit 0.3.2",
"objc2-foundation 0.3.2",
"serde",
"skrifa 0.44.0",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "craft-update"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=495e5971bc024aa96e6ed318cb6e7cd3a0103184#495e5971bc024aa96e6ed318cb6e7cd3a0103184"
source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0"
dependencies = [
"chrono",
"dunce",
@@ -1027,7 +1072,7 @@ dependencies = [
[[package]]
name = "craft-window"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=a0916808f7d78bed8c7b90cdf937176015454a7f#a0916808f7d78bed8c7b90cdf937176015454a7f"
source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0"
dependencies = [
"egui",
"objc2 0.6.5",
@@ -1599,6 +1644,17 @@ version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48"
[[package]]
name = "fixed_decimal"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2214fb00f669ee96bee5bb29dfb1ddabe058b2bc426817efc79afa03c9bc55ae"
dependencies = [
"displaydoc",
"smallvec",
"writeable",
]
[[package]]
name = "fixedbitset"
version = "0.5.7"
@@ -2117,7 +2173,7 @@ checksum = "9f4d0e94ddd48749f06bbe4e5389fb9799a0c45bcaf00495042076ef05e3241a"
[[package]]
name = "hayro-cmap"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=a0916808f7d78bed8c7b90cdf937176015454a7f#a0916808f7d78bed8c7b90cdf937176015454a7f"
source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0"
dependencies = [
"brotli",
"hayro-postscript",
@@ -2126,7 +2182,7 @@ dependencies = [
[[package]]
name = "hayro-interpret"
version = "0.7.0"
source = "git+https://github.com/storytold/craft-libs?rev=a0916808f7d78bed8c7b90cdf937176015454a7f#a0916808f7d78bed8c7b90cdf937176015454a7f"
source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0"
dependencies = [
"bitflags 2.13.2",
"hayro-cmap",
@@ -2144,7 +2200,7 @@ dependencies = [
[[package]]
name = "hayro-jbig2"
version = "0.3.0"
source = "git+https://github.com/storytold/craft-libs?rev=a0916808f7d78bed8c7b90cdf937176015454a7f#a0916808f7d78bed8c7b90cdf937176015454a7f"
source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0"
dependencies = [
"fearless_simd",
"hayro-ccitt",
@@ -2162,12 +2218,12 @@ dependencies = [
[[package]]
name = "hayro-postscript"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=a0916808f7d78bed8c7b90cdf937176015454a7f#a0916808f7d78bed8c7b90cdf937176015454a7f"
source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0"
[[package]]
name = "hayro-syntax"
version = "0.7.2"
source = "git+https://github.com/storytold/craft-libs?rev=a0916808f7d78bed8c7b90cdf937176015454a7f#a0916808f7d78bed8c7b90cdf937176015454a7f"
source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0"
dependencies = [
"flate2",
"hayro-ccitt",
@@ -2337,6 +2393,23 @@ dependencies = [
"zerovec",
]
[[package]]
name = "icu_decimal"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9eb8f655bba2d0c0459e43a5b0e6cd3cd388109898fb3d85d048165e8b0abd08"
dependencies = [
"displaydoc",
"fixed_decimal",
"icu_locale_core",
"icu_pattern",
"icu_plurals",
"icu_provider",
"serde",
"writeable",
"zerovec",
]
[[package]]
name = "icu_locale_core"
version = "2.3.0"
@@ -2382,6 +2455,9 @@ dependencies = [
"icu_properties",
"icu_provider",
"smallvec",
"utf16_iter",
"utf8_iter",
"write16",
"zerovec",
]
@@ -2391,6 +2467,32 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0"
[[package]]
name = "icu_pattern"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b3db611446bd92c246b03e4aa0bafa71ddb194af431ddbb42b9d70e4bfce1a0"
dependencies = [
"displaydoc",
"either",
"serde",
"writeable",
"zerovec",
]
[[package]]
name = "icu_plurals"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e475e6766ef87b1d3c1f97be6363995b0bfaeddbc789671615df598a97ff0593"
dependencies = [
"displaydoc",
"fixed_decimal",
"icu_provider",
"serde",
"zerovec",
]
[[package]]
name = "icu_properties"
version = "2.3.0"
@@ -2420,6 +2522,7 @@ checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73"
dependencies = [
"displaydoc",
"icu_locale_core",
"postcard",
"serde",
"stable_deref_trait",
"writeable",
@@ -2429,6 +2532,30 @@ dependencies = [
"zerovec",
]
[[package]]
name = "icu_provider_adapters"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df887bc4f55dbc773c7c45a0c7dfa75be886f7b7812830a71ab7271cc1801820"
dependencies = [
"icu_locale_fallback",
"icu_provider",
]
[[package]]
name = "icu_provider_blob"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5916b44fac319d3f58e4d8d805a627df19e27937b582cd99fcde3bb6b7ce1b0e"
dependencies = [
"icu_provider",
"postcard",
"serde",
"writeable",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_segmenter"
version = "2.3.0"
@@ -2704,7 +2831,7 @@ dependencies = [
[[package]]
name = "krilla"
version = "0.8.2"
source = "git+https://github.com/storytold/craft-libs?rev=a0916808f7d78bed8c7b90cdf937176015454a7f#a0916808f7d78bed8c7b90cdf937176015454a7f"
source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0"
dependencies = [
"base64 0.22.1",
"bumpalo",
@@ -2834,6 +2961,9 @@ name = "litemap"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae"
dependencies = [
"serde_core",
]
[[package]]
name = "litrs"
@@ -3809,6 +3939,16 @@ dependencies = [
"portable-atomic",
]
[[package]]
name = "postcard"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
dependencies = [
"cobs",
"serde",
]
[[package]]
name = "potential_utf"
version = "0.1.6"
@@ -5223,6 +5363,18 @@ dependencies = [
"syn 3.0.9",
]
[[package]]
name = "sys-locale"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4"
dependencies = [
"js-sys",
"libc",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "system-configuration"
version = "0.7.0"
@@ -5853,6 +6005,12 @@ dependencies = [
"xmlwriter",
]
[[package]]
name = "utf16_iter"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
[[package]]
name = "utf8-zero"
version = "0.8.1"
@@ -7379,11 +7537,20 @@ dependencies = [
"read-fonts 0.39.2",
]
[[package]]
name = "write16"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
[[package]]
name = "writeable"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
dependencies = [
"either",
]
[[package]]
name = "x11-dl"
@@ -7705,6 +7872,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f"
dependencies = [
"displaydoc",
"litemap",
"serde_core",
"yoke",
"zerofrom",
"zerovec",
+13 -13
View File
@@ -11,15 +11,15 @@ rust-version = "1.95"
repository = "https://github.com/storytold/vectorcraft"
[workspace.dependencies]
craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "a0916808f7d78bed8c7b90cdf937176015454a7f", default-features = false, features = ["interpret", "write"] }
craft-print = { git = "https://github.com/storytold/craft-libs", rev = "a0916808f7d78bed8c7b90cdf937176015454a7f" }
craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "a0916808f7d78bed8c7b90cdf937176015454a7f" }
craft-ui = { git = "https://github.com/storytold/craft-libs", rev = "a0916808f7d78bed8c7b90cdf937176015454a7f", features = ["serde"] }
craft-keymap = { git = "https://github.com/storytold/craft-libs", rev = "a0916808f7d78bed8c7b90cdf937176015454a7f" }
craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", default-features = false, features = ["interpret", "write"] }
craft-print = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0" }
craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0" }
craft-ui = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", features = ["serde"] }
craft-keymap = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0" }
# Decides who draws the window frame (desktop vs. app) so the suite shares one native titlebar per
# desktop. The `egui` feature adds `craft_window::decorate`. (craft-libs#217.)
craft-window = { git = "https://github.com/storytold/craft-libs", rev = "a0916808f7d78bed8c7b90cdf937176015454a7f", features = ["egui"] }
craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "495e5971bc024aa96e6ed318cb6e7cd3a0103184", features = ["egui"] }
craft-window = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", features = ["egui"] }
craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", features = ["egui"] }
# internal
vectorcraft-geom = { path = "crates/geom" }
vectorcraft-color = { path = "crates/color" }
@@ -141,12 +141,12 @@ lto = "thin"
codegen-units = 1
[patch.crates-io]
hayro-syntax = { git = "https://github.com/storytold/craft-libs", rev = "a0916808f7d78bed8c7b90cdf937176015454a7f" }
hayro-interpret = { git = "https://github.com/storytold/craft-libs", rev = "a0916808f7d78bed8c7b90cdf937176015454a7f" }
hayro-jbig2 = { git = "https://github.com/storytold/craft-libs", rev = "a0916808f7d78bed8c7b90cdf937176015454a7f" }
hayro-cmap = { git = "https://github.com/storytold/craft-libs", rev = "a0916808f7d78bed8c7b90cdf937176015454a7f" }
hayro-postscript = { git = "https://github.com/storytold/craft-libs", rev = "a0916808f7d78bed8c7b90cdf937176015454a7f" }
krilla = { git = "https://github.com/storytold/craft-libs", rev = "a0916808f7d78bed8c7b90cdf937176015454a7f" }
hayro-syntax = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0" }
hayro-interpret = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0" }
hayro-jbig2 = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0" }
hayro-cmap = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0" }
hayro-postscript = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0" }
krilla = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0" }
ecolor = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }
emath = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }
epaint = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }
+2 -2
View File
@@ -77,10 +77,10 @@ Shared PDF backend
The patched hayro-interpret 0.7.0, hayro-syntax 0.7.2, hayro-jbig2 0.3.0, hayro-cmap 0.1.0
and hayro-postscript 0.1.0 are fetched from https://github.com/storytold/craft-libs at revision
a0916808f7d78bed8c7b90cdf937176015454a7f. Their upstream project is
92d392cff132eaf53e13afff77f38b5c11b73aa0. Their upstream project is
https://github.com/LaurenzV/hayro, licensed MIT OR Apache-2.0. The snapshot retains each
crate's upstream licence files. The patch ledger is at
https://github.com/storytold/craft-libs/blob/a0916808f7d78bed8c7b90cdf937176015454a7f/vendor/README.craft-reader.md.
https://github.com/storytold/craft-libs/blob/92d392cff132eaf53e13afff77f38b5c11b73aa0/vendor/README.craft-reader.md.
Assets already bundled through Hayro's default features retain their licences:
- Foxit standard-14 substitutes: Copyright 2014 PDFium Authors. All rights reserved.
+3
View File
@@ -3,3 +3,6 @@
allow-unwrap-in-tests = true
allow-expect-in-tests = true
allow-panic-in-tests = true
disallowed-types = [
{ path = "egui::ComboBox", reason = "use craft_ui::ComboBox: egui's combo box menu can't grow back while open (egui#5225)" },
]
+2 -2
View File
@@ -13,7 +13,7 @@ pub fn status_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
let print = app.session.prefs.display_print_size;
let shown = crate::state::zoom_display_percent(zoom, print);
ui.name_inputs(tl!("Zoom"), |ui| {
egui::ComboBox::from_id_salt("zoom-combo")
craft_ui::ComboBox::from_id_salt("zoom-combo")
.selected_text(egui::RichText::new(zoom_label(zoom, print)).size(11.5))
.width(76.0)
.show_ui(ui, |ui| {
@@ -36,7 +36,7 @@ pub fn status_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
});
let rot = app.view().map(|v| v.rotation).unwrap_or(0.0);
ui.name_inputs(tl!("Rotation"), |ui| {
egui::ComboBox::from_id_salt("rot-combo")
craft_ui::ComboBox::from_id_salt("rot-combo")
.selected_text(egui::RichText::new(format!("{rot:.0}°")).size(11.5))
.width(52.0)
.show_ui(ui, |ui| {
+1 -1
View File
@@ -207,7 +207,7 @@ pub(super) fn artboard_list(app: &VectorcraftApp, ui: &mut Ui, cur: usize) -> Op
ui.add_enabled_ui(!boards.is_empty(), |ui| {
ui.set_max_width(FIELD_W);
ui.name_inputs(tl!("Artboard"), |ui| {
egui::ComboBox::from_id_salt("artboard-list")
craft_ui::ComboBox::from_id_salt("artboard-list")
.selected_text(egui::RichText::new(shown).size(11.5))
.width(FIELD_W)
.wrap_mode(egui::TextWrapMode::Truncate)
+1 -1
View File
@@ -238,7 +238,7 @@ pub fn contributors_ui(app: &mut VectorcraftApp, ui: &mut egui::Ui) {
ui.separator();
ui.label(tl!("Sort"));
ui.name_inputs(tl!("Sort"), |ui| {
egui::ComboBox::from_id_salt("credits_sort").selected_text(v.key.label().0).show_ui(ui, |ui| {
craft_ui::ComboBox::from_id_salt("credits_sort").selected_text(v.key.label().0).show_ui(ui, |ui| {
for k in SortKey::ALL {
if ui.selectable_label(v.key == k, k.label().0).clicked() {
v.key = k;
+2 -2
View File
@@ -101,7 +101,7 @@ fn precision(ui: &mut Ui, fields: &mut serde_json::Map<String, Value>, id: &str)
let mut value = before;
ui.label(tl!("Precision:"));
ui.name_inputs(tl!("Precision"), |ui| {
egui::ComboBox::from_id_salt(id).selected_text(value.to_string()).width(55.0).show_ui(ui, |ui| {
craft_ui::ComboBox::from_id_salt(id).selected_text(value.to_string()).width(55.0).show_ui(ui, |ui| {
for n in 0..=6 {
ui.selectable_value(&mut value, n, n.to_string());
}
@@ -136,7 +136,7 @@ fn body(app: &mut VectorcraftApp, ui: &mut Ui, d: &mut Dialog) -> bool {
f.insert("scale".into(), json!(scale));
}
ui.name_inputs(tl!("Scale Presets"), |ui| {
egui::ComboBox::from_id_salt("dimension-scales").selected_text(tl!("Presets")).width(76.0).show_ui(ui, |ui| {
craft_ui::ComboBox::from_id_salt("dimension-scales").selected_text(tl!("Presets")).width(76.0).show_ui(ui, |ui| {
for scale in ["1:1", "1:2", "1:5", "1:10", "1:20", "1:50", "1:100", "1:200", "2:1", "5:1", "10:1"] {
if ui.selectable_label(false, scale).clicked() {
f.insert("scale".into(), json!(scale));
+1 -1
View File
@@ -122,7 +122,7 @@ const CHIP: egui::Vec2 = egui::vec2(16.0, 10.0);
fn color_dropdown(ui: &mut egui::Ui, shown: &str) -> Option<usize> {
let border = crate::theme::Tokens::get(ui.ctx()).input_border;
let rows = LAYER_COLORS.iter().map(|(n, c)| (*n, Some(*c))).chain([("Custom", None)]);
widgets::combo(ui, "layer-color", tl!(shown), 140.0, false, |ui| {
widgets::combo(ui, "layer-color", tl!(shown), 140.0, |ui| {
let mut chosen = None;
for (i, (name, rgb)) in rows.enumerate() {
let chip = egui::IdSalt::new(("layer-color-chip", i));
+45 -34
View File
@@ -923,40 +923,51 @@ fn rows_section(ui: &mut egui::Ui, d: &mut Dialog, review: &mut Review, statuses
list.inner.inner_rect.height()
}
/// Row `r`'s target picker: "No equivalent" and the row's choices, then Keyboard Shortcuts' entries
/// that match the search. → the target picked ("" for no equivalent).
fn target_picker(ui: &mut egui::Ui, i: usize, r: &Row) -> Option<String> {
/// Row `r`'s target picker: the search field, "No equivalent" and the row's choices stay at the
/// top, over Keyboard Shortcuts' entries that match the search, which scroll. → the target picked
/// ("" for no equivalent).
pub(super) fn target_picker(ui: &mut egui::Ui, i: usize, r: &Row) -> Option<String> {
let current = target_label(r.target.as_deref());
widgets::combo(ui, ("shortcut-import-target", i), &current, TARGET_W, true, |ui| {
let field = ui.scope_id().with("query");
if ui.memory(|m| m.focused().is_none()) {
ui.memory_mut(|m| m.request_focus(field.with("edit")));
}
let q = widgets::search_field(ui, field, tl!("Search")).to_lowercase();
let mut picked = None;
let mut option = |ui: &mut egui::Ui, key: &str, label: &str| {
if ui.add(egui::Button::selectable(r.target.as_deref() == Some(key), label)).clicked() {
picked = Some(key.to_string());
}
};
option(ui, "", tl!("No equivalent"));
let list: Vec<&Entry> = if q.is_empty() {
for e in r.choices.iter().filter_map(|c| entry(c)) {
option(ui, &e.key, &shown(e));
}
ui.separator();
shortcut_editor::entries().iter().collect()
} else {
shortcut_editor::entries().iter().filter(|e| shown(e).to_lowercase().contains(&q) || e.key.to_lowercase().contains(&q)).collect()
};
egui::ScrollArea::vertical().max_height(300.0).show_rows(ui, ROW_H, list.len(), |ui, range| {
for e in range.filter_map(|k| list.get(k)) {
option(ui, &e.key, &shown(e));
}
});
if picked.is_some() {
ui.close();
}
picked
// The entries' list is up to 300 points tall under the header, which was this tall last frame.
let header_id = egui::Id::unique(("shortcut-import-target-header", i));
let height = ui.data(|d| d.get_temp::<f32>(header_id)).unwrap_or(0.0) + 300.0;
let option = |ui: &mut egui::Ui, key: &str, label: &str| {
ui.add(egui::Button::selectable(r.target.as_deref() == Some(key), label)).clicked().then(|| key.to_string())
};
widgets::combo_menu(ui, ("shortcut-import-target", i), &current, TARGET_W, |menu| {
menu.height(height).show_rows_with_header(
|ui| {
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
let field = ui.scope_id().with("query");
if ui.memory(|m| m.focused().is_none()) {
ui.memory_mut(|m| m.request_focus(field.with("edit")));
}
let q = widgets::search_field(ui, field, tl!("Search")).to_lowercase();
let mut picked = option(ui, "", tl!("No equivalent"));
let list: Vec<&Entry> = if q.is_empty() {
for e in r.choices.iter().filter_map(|c| entry(c)) {
picked = option(ui, &e.key, &shown(e)).or(picked);
}
ui.separator();
shortcut_editor::entries().iter().collect()
} else {
shortcut_editor::entries().iter().filter(|e| shown(e).to_lowercase().contains(&q) || e.key.to_lowercase().contains(&q)).collect()
};
ui.data_mut(|d| d.insert_temp(header_id, ui.min_rect().height()));
(list, picked)
},
ROW_H,
|(list, _)| list.len(),
|ui, range, (list, mut picked)| {
for e in range.filter_map(|k| list.get(k)) {
picked = option(ui, &e.key, &shown(e)).or(picked);
}
if picked.is_some() {
ui.close();
}
picked
},
)
})
.flatten()
}
@@ -7,7 +7,7 @@ use craft_keymap::import::namematch::Candidate;
use serde_json::{Value, json};
use vectorcraft_engine::Session;
use super::shortcut_import::{Code, KIND, Review, Row, Status, report};
use super::shortcut_import::{Code, KIND, Review, Row, Status, report, target_picker};
use super::*;
use crate::{io, shortcut_editor, theme};
@@ -576,3 +576,58 @@ fn long_names_match_and_apply() {
let (out, ov) = apply_rows(&mut app, "defaults", &[(&name, "Menus", "edit.copy", "Cmd+Alt+Shift+F11")]);
assert_eq!((out["applied"].clone(), ov), (json!(1), json!({"edit.copy": "Cmd+Alt+Shift+F11"})));
}
/// A search in a row's target picker that matches nothing shrinks its menu to the search field
/// and "No equivalent"; clearing the search lists every entry again, and the menu grows back to
/// its height before the search (egui#5225). The search field stays where it was throughout.
#[test]
fn the_target_picker_grows_back_when_a_search_that_matched_nothing_is_cleared() {
let ctx = egui::Context::default();
theme::install_fonts(&ctx);
theme::apply(&ctx, Default::default());
let r = Row { name: "Acme Undo".into(), ..Default::default() };
let frame = |events: Vec<egui::Event>| {
let input =
egui::RawInput { events, screen_rect: Some(egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(600.0, 800.0))), ..Default::default() };
let mut at = egui::Pos2::ZERO;
let mut out = ctx.run_ui(input, |ui| {
at = ui.next_widget_position();
target_picker(ui, 0, &r);
});
out.textures_delta.clear();
let text = |label: &str| {
out.shapes.iter().find_map(|s| match &s.shape {
egui::Shape::Text(t) if t.galley.text() == label => Some(t.pos),
_ => None,
})
};
// The row under the search field: it shows whatever the field holds.
let none_row = text("No equivalent");
let menu = none_row.and_then(|p| ctx.layer_id_at(p)).and_then(|l| ctx.memory(|m| m.area_rect(l.id)));
(at, none_row, text("Search"), menu.map(|r| r.height()))
};
let settle = |events: Vec<egui::Event>| {
let mut last = frame(events);
for _ in 0..5 {
last = frame(vec![]);
}
last
};
let (at, ..) = frame(vec![]);
let click = at + egui::vec2(40.0, 10.0);
let button = |pressed| egui::Event::PointerButton { pos: click, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() };
frame(vec![egui::Event::PointerMoved(click), button(true)]);
let (_, none_row, search, full) = settle(vec![button(false)]);
let (none_row, search, full) = (none_row.expect("the menu is open"), search.expect("the search field shows"), full.expect("the menu's area"));
let nothing = "qqzzxxqqzz";
let (_, n, _, small) = settle(vec![egui::Event::Text(nothing.into())]);
assert_eq!(n, Some(none_row), "the search field stays put while nothing matches");
let small = small.expect("the menu stays open");
assert!(small + 40.0 < full, "the menu shrinks to its search field: {small} of {full}");
for _ in nothing.chars() {
frame(vec![press(egui::Key::Backspace)]);
}
let (_, n, s, grown) = settle(vec![]);
assert_eq!((n, s), (Some(none_row), Some(search)), "the search field is where it was");
assert_eq!(grown, Some(full), "the menu grows back to its height before the search");
}
+3 -3
View File
@@ -555,10 +555,10 @@ fn warp_options_style_keyboard_enter_does_not_accept_dialog() {
assert_ne!(ctx.memory(|m| m.focused()), Some(style_id));
warp_key(&mut app, &ctx, egui::Key::Tab, egui::Modifiers::SHIFT);
assert_eq!(ctx.memory(|m| m.focused()), Some(style_id));
assert!(!egui::ComboBox::is_open(&ctx, style_id));
assert!(!craft_ui::ComboBox::is_open(&ctx, style_id));
warp_key(&mut app, &ctx, egui::Key::Enter, egui::Modifiers::NONE);
assert!(app.ui.dialog.is_some(), "WARP_STYLE_ENTER_OPEN: opening Style must not accept Warp Options");
assert!(egui::ComboBox::is_open(&ctx, style_id));
assert!(craft_ui::ComboBox::is_open(&ctx, style_id));
assert_eq!(app.ui.dialog.as_ref().unwrap().str("__effect"), "warp.arc");
assert_eq!(entries(&app.session.doc().unwrap().history.undo), undo);
assert_eq!(entries(&app.session.doc().unwrap().history.redo), redo);
@@ -567,7 +567,7 @@ fn warp_options_style_keyboard_enter_does_not_accept_dialog() {
tab_to_warp_control(&mut app, &ctx, "Arc Lower", egui::Modifiers::NONE);
warp_key(&mut app, &ctx, egui::Key::Enter, egui::Modifiers::NONE);
assert!(app.ui.dialog.is_some(), "WARP_STYLE_ENTER_CHOICE: choosing Style must not accept Warp Options");
assert!(!egui::ComboBox::is_open(&ctx, style_id));
assert!(!craft_ui::ComboBox::is_open(&ctx, style_id));
assert_eq!(app.ui.dialog.as_ref().unwrap().str("__effect"), "warp.arcLower");
assert!(app.ui.dialog.as_ref().unwrap().bool("__warp_style_changed"));
let state = app.session.doc().unwrap();
+1 -1
View File
@@ -71,7 +71,7 @@ pub(super) fn body(ui: &mut egui::Ui, d: &mut Dialog) {
let current = WARP_STYLES.iter().find(|(style, _)| *style == suffix).map_or_else(|| suffix.to_string(), |(_, label)| shown(label));
widgets::label_row(ui, tl!("Style:"), LABEL_W, |ui| {
style_preview(ui, suffix);
if let Some((style, label)) = widgets::combo(ui, "warp-style", &current, 170.0, false, |ui| {
if let Some((style, label)) = widgets::combo(ui, "warp-style", &current, 170.0, |ui| {
let mut chosen = None;
for (style, label) in WARP_STYLES {
ui.horizontal(|ui| {
+148 -108
View File
@@ -82,6 +82,8 @@ const STAR: f32 = 18.0;
const NAME_SHARE: f32 = 0.5;
/// Width of the disclosure triangle of a family with several styles.
const DISCLOSURE: f32 = 14.0;
/// The list's greatest height, under the search field and filters.
const LIST_HEIGHT: f32 = 600.0;
/// Samples rendered (or asked for) per frame, across every menu, so a long list doesn't stall the UI.
const SAMPLES_PER_FRAME: usize = 4;
/// Samples rendering at once (native).
@@ -231,8 +233,8 @@ struct MenuState {
/// The rows listed, and what for: built again only when that changes, not every frame.
rows: Arc<[Row]>,
rows_key: Option<RowsKey>,
/// The list's scroll offset last frame.
scrolled: f32,
/// The height of the search field and filters last frame.
header: f32,
}
/// The font family menu showing `current`, `width` points wide, rows sampling `sample` (the
@@ -247,12 +249,21 @@ pub fn font_menu(
) -> Option<FontPick> {
let state_id = ui.scope_id().with(&id).with("font-menu");
let mut st: MenuState = ui.data(|d| d.get_temp(state_id)).unwrap_or_default();
let mut drawn = false;
let picked = widgets::combo(ui, &id, current, width, true, |ui| {
drawn = true;
list(ui, state_id, &mut st, current, sample, look)
let height = st.header + LIST_HEIGHT;
let state = &mut st;
// The search field and filters stay in place over the rows, which scroll. The rows are all
// one height (a family's styles too), so only those in view are laid out: there are thousands
// of families.
let shown = widgets::combo_menu(ui, &id, current, width, |menu| {
menu.height(height).show_rows_with_header(
|ui| header(ui, state_id, state, current, look),
look.row,
|h| h.rows.len(),
|ui, range, h| rows(ui, range, h, sample, look),
)
});
let mut out = picked;
let drawn = shown.is_some();
let mut out = shown.flatten();
if !drawn && out.is_none() && st.previewed.take().is_some() {
// Closed (Escape, a click outside) with a preview showing.
out = Some(FontPick::EndPreview);
@@ -264,10 +275,33 @@ pub fn font_menu(
out
}
/// The list inside the open menu.
/// What the menu's header hands its rows.
struct Listed<'s> {
st: &'s mut MenuState,
/// The rows listed.
rows: Arc<[Row]>,
/// The current family by its own name.
current: String,
/// Enter was pressed; ↑/↓/←/→ moved the highlight; the menu is opening.
enter: bool,
keyed: bool,
opening: bool,
}
/// The menu's contents outside a menu, for the unit tests: the header over its rows, as
/// [`craft_ui::combo::Menu::show_rows_with_header`] lays them out.
#[cfg(test)]
fn list(ui: &mut Ui, state_id: egui::Id, st: &mut MenuState, current: &str, sample: Option<&str>, look: MenuLook) -> Option<FontPick> {
let row_h = look.row;
let h = header(ui, state_id, st, current, look);
let total = h.rows.len();
egui::ScrollArea::vertical().max_height(ui.available_height()).show_rows(ui, look.row, total, |ui, range| rows(ui, range, h, sample, look)).inner
}
/// The top of the open menu, which stays in place: the search field, the filters, and the rows
/// they list.
fn header<'s>(ui: &mut Ui, state_id: egui::Id, st: &'s mut MenuState, current: &str, look: MenuLook) -> Listed<'s> {
let t = Tokens::get(ui.ctx());
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
// Each time the menu opens: no query, the current family highlighted and in view (the
// filters stay as they were).
let pass = ui.ctx().cumulative_pass_nr();
@@ -407,122 +441,128 @@ fn list(ui: &mut Ui, state_id: egui::Id, st: &mut MenuState, current: &str, samp
}
keyed = true;
}
st.header = ui.min_rect().height();
if rows.is_empty() {
widgets::dim_label(ui, tl!("No matching fonts"));
}
ui.spacing_mut().item_spacing.y = 0.0;
// The scroll bar beside the rows, not over them: a floating one covered the stars at their
// right ends, so a favourite was hard to click (#2277).
ui.spacing_mut().scroll = widgets::list_scroll_style();
Listed { st, rows, current: current.to_string(), enter, keyed, opening }
}
/// The rows of the open menu in `range`, the ones in view: the menu's one scrolling part, under
/// the search field and filters (#555).
fn rows(ui: &mut Ui, range: std::ops::Range<usize>, h: Listed<'_>, sample: Option<&str>, look: MenuLook) -> Option<FontPick> {
let Listed { st, rows, current, enter, keyed, opening } = h;
let current = current.as_str();
let row_h = look.row;
let t = Tokens::get(ui.ctx());
let db = vectorcraft_text::FontDb::global();
let favorite = |f: &str| look.favorites.iter().any(|x| x.eq_ignore_ascii_case(f));
if rows.is_empty() {
// A filter or a font rescan can remove the row whose font was previewed.
// Do not leave that font applied while the list has no candidate to highlight.
return st.previewed.take().map(|_| FontPick::EndPreview);
}
// The rows: only those in view are laid out (there are thousands of families). The list is
// the menu's one scrolling part, under the search field and filters (#555): it fills the
// menu's room (its max rect) below them.
let room = (ui.max_rect().bottom() - ui.next_widget_position().y).clamp(120.0, 600.0);
let mut area = egui::ScrollArea::vertical().max_height(room).min_scrolled_height(room.min(row_h * rows.len() as f32));
// Scrolls the list to row `i`'s place, laid out or not: the rows in view start at
// `range.start`.
let (left, width) = (ui.max_rect().left(), ui.max_rect().width());
let top = ui.max_rect().top() - range.start as f32 * row_h;
let scroll_to = |ui: &Ui, i: usize, align| {
let place = egui::Rect::from_min_size(egui::pos2(left, top + i as f32 * row_h), vec2(width, row_h));
ui.scroll_to_rect_animation(place, align, egui::style::ScrollAnimation::none());
};
// Open at the recent section even when the current family is farther down the list.
// Otherwise centre the current font; subsequent key presses keep the highlight in view.
if opening && matches!(rows.first(), Some(Row::Heading("Recent Fonts"))) {
area = area.vertical_scroll_offset(0.0);
} else if let Some(top) = st.highlight.map(|i| i as f32 * row_h) {
let target = if opening {
Some(top - (room - row_h) * 0.5)
} else if keyed && top < st.scrolled {
Some(top)
} else if keyed && top + row_h > st.scrolled + room {
Some(top + row_h - room)
} else {
None
};
if let Some(y) = target {
area = area.vertical_scroll_offset(y.max(0.0));
}
scroll_to(ui, 0, Some(egui::Align::TOP));
} else if let Some(i) = st.highlight
&& (opening || keyed)
{
scroll_to(ui, i, opening.then_some(egui::Align::Center));
}
let mut chosen: Option<(String, Option<String>)> = None;
let mut hovered: Option<usize> = None;
let mut toggled: Option<String> = None;
let mut starred: Option<String> = None;
ui.spacing_mut().item_spacing.y = 0.0;
// The scroll bar beside the rows, not over them: a floating one covered the stars at their
// right ends, so a favourite was hard to click (#2277).
ui.spacing_mut().scroll = widgets::list_scroll_style();
let shown = area.show_rows(ui, row_h, rows.len(), |ui, range| {
for i in range {
let Some(row) = rows.get(i) else { continue };
if let Row::Heading(label) = row {
let (rect, _) = ui.allocate_exact_size(vec2(ui.available_width(), row_h), Sense::hover());
// Existing translated menu labels; the heading cannot preview or apply a font.
let label = match *label {
"Recent Fonts" => tl!("Recent Fonts"),
_ => tl!("Fonts"),
};
ui.painter_at(rect).text(
rect.left_center() + vec2(4.0, 0.0),
egui::Align2::LEFT_CENTER,
label,
egui::FontId::proportional(11.0),
t.text_dim,
);
continue;
}
let Some(family) = row.family() else { continue };
let (rect, resp) = ui.allocate_exact_size(vec2(ui.available_width(), row_h), Sense::click());
if resp.hovered() && ui.input(|inp| inp.pointer.delta() != egui::Vec2::ZERO || inp.smooth_scroll_delta != egui::Vec2::ZERO) {
hovered = Some(i);
}
let p = ui.painter_at(rect);
if st.highlight == Some(i) {
p.rect_filled(rect, 2.0, t.selection);
} else if matches!(row, Row::Family(f) if f.eq_ignore_ascii_case(current)) {
p.rect_filled(rect, 2.0, t.hover);
}
// The disclosure triangle of a family with several styles.
let tri = egui::Rect::from_min_size(rect.min, vec2(DISCLOSURE, row_h));
let mut on_triangle = false;
if let Row::Family(f) = row
&& db.styles(f).len() > 1
{
let open = st.expanded.iter().any(|e| e.eq_ignore_ascii_case(f));
p.text(tri.center(), egui::Align2::CENTER_CENTER, if open { "▾" } else { "▸" }, egui::FontId::proportional(11.0), t.text_dim);
on_triangle = resp.interact_pointer_pos().is_some_and(|pos| tri.contains(pos));
if resp.clicked() && on_triangle {
toggled = Some(f.to_lowercase());
}
}
// The star at the end of a family's row.
let star = egui::Rect::from_min_max(egui::pos2(rect.max.x - STAR, rect.min.y), rect.max);
let mut on_star = false;
if let Row::Family(f) = row {
let on = favorite(f);
let hot = resp.hover_pos().is_some_and(|pos| star.contains(pos));
if on || hot || st.highlight == Some(i) {
let color = if on { t.accent } else { t.text_dim };
p.text(star.center(), egui::Align2::CENTER_CENTER, if on { "★" } else { "☆" }, egui::FontId::proportional(12.0), color);
}
on_star = resp.interact_pointer_pos().is_some_and(|pos| star.contains(pos));
if resp.clicked() && on_star {
starred = Some(f.clone());
}
}
let indent = if row.style().is_some() { DISCLOSURE + 12.0 } else { DISCLOSURE };
let name_w = rect.width() * NAME_SHARE;
let name_rect = egui::Rect::from_min_max(rect.min + vec2(indent, 0.0), egui::pos2(rect.min.x + name_w - 4.0, rect.max.y));
// Family rows follow Show Font Names in English; style rows keep the style name (#394).
let name = match row {
Row::Style(_, style) => style.clone(),
Row::Family(f) => db.family_display_name(f, look.english_names),
Row::Heading(_) => continue,
for i in range {
let Some(row) = rows.get(i) else { continue };
if let Row::Heading(label) = row {
let (rect, _) = ui.allocate_exact_size(vec2(ui.available_width(), row_h), Sense::hover());
// Existing translated menu labels; the heading cannot preview or apply a font.
let label = match *label {
"Recent Fonts" => tl!("Recent Fonts"),
_ => tl!("Fonts"),
};
ui.painter_at(name_rect).text(name_rect.left_center(), egui::Align2::LEFT_CENTER, name, egui::FontId::proportional(12.0), t.text);
let sample_rect = egui::Rect::from_min_max(rect.min + vec2(name_w, 2.0), rect.max - vec2(STAR + 2.0, 2.0));
if !look.samples {
} else if let Some(tex) = sample_texture(ui.ctx(), family, row.style(), sample, sample_rect.height()) {
paint_sample(ui, sample_rect, &tex, t.text);
}
if resp.clicked() && !on_triangle && !on_star {
chosen = row.pick();
ui.painter_at(rect).text(
rect.left_center() + vec2(4.0, 0.0),
egui::Align2::LEFT_CENTER,
label,
egui::FontId::proportional(11.0),
t.text_dim,
);
continue;
}
let Some(family) = row.family() else { continue };
let (rect, resp) = ui.allocate_exact_size(vec2(ui.available_width(), row_h), Sense::click());
if resp.hovered() && ui.input(|inp| inp.pointer.delta() != egui::Vec2::ZERO || inp.smooth_scroll_delta != egui::Vec2::ZERO) {
hovered = Some(i);
}
let p = ui.painter_at(rect);
if st.highlight == Some(i) {
p.rect_filled(rect, 2.0, t.selection);
} else if matches!(row, Row::Family(f) if f.eq_ignore_ascii_case(current)) {
p.rect_filled(rect, 2.0, t.hover);
}
// The disclosure triangle of a family with several styles.
let tri = egui::Rect::from_min_size(rect.min, vec2(DISCLOSURE, row_h));
let mut on_triangle = false;
if let Row::Family(f) = row
&& db.styles(f).len() > 1
{
let open = st.expanded.iter().any(|e| e.eq_ignore_ascii_case(f));
p.text(tri.center(), egui::Align2::CENTER_CENTER, if open { "▾" } else { "▸" }, egui::FontId::proportional(11.0), t.text_dim);
on_triangle = resp.interact_pointer_pos().is_some_and(|pos| tri.contains(pos));
if resp.clicked() && on_triangle {
toggled = Some(f.to_lowercase());
}
}
});
st.scrolled = shown.state.clamped_offset().y;
// The star at the end of a family's row.
let star = egui::Rect::from_min_max(egui::pos2(rect.max.x - STAR, rect.min.y), rect.max);
let mut on_star = false;
if let Row::Family(f) = row {
let on = favorite(f);
let hot = resp.hover_pos().is_some_and(|pos| star.contains(pos));
if on || hot || st.highlight == Some(i) {
let color = if on { t.accent } else { t.text_dim };
p.text(star.center(), egui::Align2::CENTER_CENTER, if on { "★" } else { "☆" }, egui::FontId::proportional(12.0), color);
}
on_star = resp.interact_pointer_pos().is_some_and(|pos| star.contains(pos));
if resp.clicked() && on_star {
starred = Some(f.clone());
}
}
let indent = if row.style().is_some() { DISCLOSURE + 12.0 } else { DISCLOSURE };
let name_w = rect.width() * NAME_SHARE;
let name_rect = egui::Rect::from_min_max(rect.min + vec2(indent, 0.0), egui::pos2(rect.min.x + name_w - 4.0, rect.max.y));
// Family rows follow Show Font Names in English; style rows keep the style name (#394).
let name = match row {
Row::Style(_, style) => style.clone(),
Row::Family(f) => db.family_display_name(f, look.english_names),
Row::Heading(_) => continue,
};
ui.painter_at(name_rect).text(name_rect.left_center(), egui::Align2::LEFT_CENTER, name, egui::FontId::proportional(12.0), t.text);
let sample_rect = egui::Rect::from_min_max(rect.min + vec2(name_w, 2.0), rect.max - vec2(STAR + 2.0, 2.0));
if !look.samples {
} else if let Some(tex) = sample_texture(ui.ctx(), family, row.style(), sample, sample_rect.height()) {
paint_sample(ui, sample_rect, &tex, t.text);
}
if resp.clicked() && !on_triangle && !on_star {
chosen = row.pick();
}
}
if let Some(f) = toggled {
if st.expanded.contains(&f) {
st.expanded.retain(|e| *e != f);
+3 -3
View File
@@ -260,7 +260,7 @@ pub(crate) fn trace_button(app: &mut VectorcraftApp, ui: &mut Ui, width: f32) {
/// The selected Image Trace object's preset (Control bar, Properties): choosing another traces it
/// again with that preset.
pub(crate) fn preset_dropdown(app: &mut VectorcraftApp, ui: &mut Ui, current: &str, width: f32) {
if let Some(p) = widgets::combo(ui, "trace-preset", tl!(current), width, false, |ui| preset_list(app, ui, current)) {
if let Some(p) = widgets::combo(ui, "trace-preset", tl!(current), width, |ui| preset_list(app, ui, current)) {
trace_with(app, &p);
}
}
@@ -436,7 +436,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
ui.horizontal(|ui| {
widgets::dim_label(ui, tl!("Library:"));
let key = st.params["library"].as_str().unwrap_or(DOCUMENT_SWATCHES).to_string();
if let Some(k) = widgets::combo(ui, "it-library", &library_label(app, &key), 170.0, false, |ui| library_list(app, ui, &key)) {
if let Some(k) = widgets::combo(ui, "it-library", &library_label(app, &key), 170.0, |ui| library_list(app, ui, &key)) {
if k != DOCUMENT_SWATCHES {
st.params["colorGroup"] = Value::Null;
}
@@ -451,7 +451,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
widgets::dim_label(ui, tl!("Colors:"));
let group = color_group(app, &st.params).map(str::to_string);
let label = group.clone().unwrap_or_else(|| tl!("All").to_string());
if let Some(g) = widgets::combo(ui, "it-color-group", &label, 170.0, false, |ui| color_group_list(app, ui, group.as_deref())) {
if let Some(g) = widgets::combo(ui, "it-color-group", &label, 170.0, |ui| color_group_list(app, ui, group.as_deref())) {
st.params["colorGroup"] = json!(g);
st.preset = "Custom".into();
retrace = true;
+1 -1
View File
@@ -525,7 +525,7 @@ fn artboard_size_controls_inner(
let mut fit = None;
ui.horizontal(|ui| {
dim_label(ui, tl!("Preset:"));
preset = widgets::combo(ui, "ab-preset", &current, 140.0, false, |ui| {
preset = widgets::combo(ui, "ab-preset", &current, 140.0, |ui| {
let mut chosen = None;
if let Some(enabled) = fits {
for (selection, label) in [(false, tl!("Fit to Artwork Bounds")), (true, tl!("Fit to Selected Art"))] {
+55
View File
@@ -395,6 +395,61 @@ fn the_search_field_stays_put_while_the_list_scrolls() {
}
}
/// A search that matches nothing shrinks the menu to its search field and filters; clearing the
/// search lists every family again, and the menu grows back to its height before the search
/// (egui#5225). The search field stays where it was throughout.
#[test]
fn the_menu_grows_back_when_a_search_that_matched_nothing_is_cleared() {
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
let frame = |events: Vec<egui::Event>| {
let input =
egui::RawInput { events, screen_rect: Some(egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(600.0, 800.0))), ..Default::default() };
let mut at = egui::Pos2::ZERO;
let mut out = ctx.run_ui(input, |ui| {
at = ui.next_widget_position();
font_menu(ui, "test-regrow", "Inter", 260.0, None, MenuLook { samples: false, ..Default::default() });
});
out.textures_delta.clear();
let text = |label: &str| {
out.shapes.iter().find_map(|s| match &s.shape {
egui::Shape::Text(t) if t.galley.text() == label => Some(t.pos),
_ => None,
})
};
// The filter row under the search field: it shows whatever the field holds.
let filters = text("Any Kind");
let menu = filters.and_then(|p| ctx.layer_id_at(p)).and_then(|l| ctx.memory(|m| m.area_rect(l.id)));
(at, filters, text("Search"), text("No matching fonts").is_some(), menu.map(|r| r.height()))
};
let settle = |events: Vec<egui::Event>| {
let mut last = frame(events);
for _ in 0..5 {
last = frame(vec![]);
}
last
};
let (at, ..) = frame(vec![]);
let click = at + egui::vec2(40.0, 10.0);
let button = |pressed| egui::Event::PointerButton { pos: click, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() };
frame(vec![egui::Event::PointerMoved(click), button(true)]);
let (_, filters, search, _, full) = settle(vec![button(false)]);
let (filters, search, full) = (filters.expect("the menu is open"), search.expect("the search field shows"), full.expect("the menu's area"));
let nothing = "qqzzxxqqzz";
let (_, f, _, none, small) = settle(vec![egui::Event::Text(nothing.into())]);
assert!(none, "the search matches no family");
assert_eq!(f, Some(filters), "the search field stays put while nothing matches");
let small = small.expect("the menu stays open");
assert!(small + MenuLook::default().row < full, "the menu shrinks to its search field: {small} of {full}");
for _ in nothing.chars() {
frame(key(egui::Key::Backspace));
}
let (_, f, s, none, grown) = settle(vec![]);
assert!(!none, "every family is listed again");
assert_eq!((f, s), (Some(filters), Some(search)), "the search field is where it was");
assert_eq!(grown, Some(full), "the menu grows back to its height before the search");
}
/// #563: a preview stays on the text while the menu stays on its family. The menu's caller passes
/// the text's font, which the preview changed; the menu keeps the font from before the preview as
/// the current one, so it doesn't take the previewed family for it and end the preview, then
+1 -1
View File
@@ -344,7 +344,7 @@ fn escape_after_closing_a_popup_or_during_a_ruler_origin_drag_keeps_the_rotation
let combo = combo.expect("the status bar's angle menu");
w.click(0.0, egui::PointerButton::Primary, combo.rect.center());
w.after(0.0, vec![]);
assert!(egui::ComboBox::is_open(&w.ctx, combo.id), "the angle menu is open");
assert!(craft_ui::ComboBox::is_open(&w.ctx, combo.id), "the angle menu is open");
w.escape(0.2);
assert!(!egui::Popup::is_any_open(&w.ctx), "Escape closes it");
w.escape(0.2);
+2 -2
View File
@@ -38,9 +38,9 @@ pub use color::{
color_slider_named, color_slider_stepped, color_slider_valued, harmony_wheel, hex_field, hex_field_named, region_texture,
};
pub use drag::{PanelDrag, SwatchRows, art_drop, doc_preview, drag_source};
pub(crate) use dropdowns::combo;
#[cfg(test)]
use dropdowns::combo_button_id;
pub(crate) use dropdowns::{combo, combo_menu};
pub use dropdowns::{
dropdown, dropdown_named, dropdown_names, dropdown_names_named, dropdown_with, dropdown_with_named, list_scroll_style, menu_room, menu_scroll,
panel_scroll, popover, set_menu_floor, strip_scroll,
@@ -227,7 +227,7 @@ pub(crate) mod accessibility_tests {
angle_dial(ui, "angle", 45.0, 28.0);
channel_slider_named(ui, "channel", "H", 0.5, vec2(22.0, 80.0), &|_| Color32::GRAY);
harmony_wheel(ui, "wheel", 120.0, &[red], Some(0));
combo(ui, "font", "Regular", 120.0, true, |_| None::<usize>);
combo_menu(ui, "font", "Regular", 120.0, |_| None::<usize>);
});
let slider = |label| {
let n = named(&tree, label);
+1 -1
View File
@@ -13,7 +13,7 @@ pub fn blend_separator_before(i: usize) -> bool {
/// The blend-mode dropdown, its groups separated (Normal | darken | lighten | contrast | inversion
/// | component modes). `None` (objects that differ) shows blank. Returns the chosen mode.
pub fn blend_dropdown(ui: &mut Ui, id: impl std::hash::Hash, current: Option<BlendMode>, width: f32) -> Option<BlendMode> {
combo(ui, id, current.map_or("", |m| tl!(m.label())), width, false, |ui| {
combo(ui, id, current.map_or("", |m| tl!(m.label())), width, |ui| {
let mut chosen = None;
for (i, m) in BlendMode::ALL.into_iter().enumerate() {
if blend_separator_before(i) {
+38 -51
View File
@@ -21,7 +21,7 @@ pub fn dropdown_names(ui: &mut Ui, id: impl std::hash::Hash, current: &str, opti
/// [`dropdown_names`] for a row whose visible label is not an adjacent label widget.
pub fn dropdown_names_named(ui: &mut Ui, id: impl std::hash::Hash, name: Option<&str>, current: &str, options: &[&str], width: f32) -> Option<usize> {
combo_named(ui, id, name, current, width, false, |ui| {
combo_named(ui, id, name, current, width, |ui| {
let mut chosen = None;
for (i, o) in options.iter().enumerate() {
if ui.add(egui::Button::selectable(*o == current, *o)).clicked() {
@@ -55,7 +55,7 @@ pub fn dropdown_with_named(
width: f32,
enabled: impl Fn(usize) -> bool,
) -> Option<usize> {
combo_named(ui, id, name, tl!(current), width, false, |ui| {
combo_named(ui, id, name, tl!(current), width, |ui| {
let mut chosen = None;
for (i, o) in options.iter().enumerate() {
if ui.add_enabled(enabled(i), egui::Button::selectable(*o == current, tl!(o))).clicked() {
@@ -66,7 +66,7 @@ pub fn dropdown_with_named(
})
}
/// The id egui's [`egui::ComboBox::from_id_salt`] gives its button in this `Ui`: `from_id_salt`
/// The id [`craft_ui::ComboBox::from_id_salt`] gives its button in this `Ui`: `from_id_salt`
/// stores `IdSalt::new(salt)` and hashes it once more with the scope, so the app must hash `id`
/// the same way to read the button's focus and open state before it is drawn.
pub(super) fn combo_button_id(ui: &Ui, id: &impl std::hash::Hash) -> egui::Id {
@@ -74,17 +74,9 @@ pub(super) fn combo_button_id(ui: &Ui, id: &impl std::hash::Hash) -> egui::Id {
}
/// The recessed combo box of [`dropdown`] showing `current`; `list` draws the options and returns
/// the chosen one. A `searchable` list (a search field over a list it scrolls itself) stays open
/// while it is clicked: it closes itself when an option is chosen.
pub(crate) fn combo<R>(
ui: &mut Ui,
id: impl std::hash::Hash,
current: &str,
width: f32,
searchable: bool,
list: impl FnOnce(&mut Ui) -> Option<R>,
) -> Option<R> {
combo_named(ui, id, None, current, width, searchable, list)
/// the chosen one.
pub(crate) fn combo<R>(ui: &mut Ui, id: impl std::hash::Hash, current: &str, width: f32, list: impl FnOnce(&mut Ui) -> Option<R>) -> Option<R> {
combo_named(ui, id, None, current, width, list)
}
/// [`combo`] for a row whose visible label is not an adjacent label widget.
@@ -94,7 +86,6 @@ pub(crate) fn combo_named<R>(
name: Option<&str>,
current: &str,
width: f32,
searchable: bool,
list: impl FnOnce(&mut Ui) -> Option<R>,
) -> Option<R> {
let t = Tokens::get(ui.ctx());
@@ -104,12 +95,9 @@ pub(crate) fn combo_named<R>(
.stroke(Stroke::new(1.0, t.input_border))
.corner_radius(CornerRadius::same(3))
.show(ui, |ui| {
if searchable {
return searchable_combo(ui, id_with(ui.scope_id(), id), label, name, current, width - 4.0, list);
}
let button_id = combo_button_id(ui, &id);
let enter_for_combo = ui.memory(|m| m.has_focus(button_id)) || egui::ComboBox::is_open(ui.ctx(), button_id);
let response = egui::ComboBox::from_id_salt(HashOnly(&id))
let enter_for_combo = ui.memory(|m| m.has_focus(button_id)) || craft_ui::ComboBox::is_open(ui.ctx(), button_id);
let response = craft_ui::ComboBox::from_id_salt(HashOnly(&id))
.selected_text(egui::RichText::new(current).size(12.0))
.width(width - 4.0)
.height(ui.spacing().combo_height)
@@ -132,20 +120,35 @@ pub(crate) fn combo_named<R>(
.inner
}
/// A dropdown whose list keeps its own search field and scrolling rows (the font menu): drawn as
/// egui's combo box, but in a popup without the scroll area egui puts around a combo's list, so
/// the field stays at the top and only the rows scroll (#555). A click outside closes it.
fn searchable_combo<R>(
/// The recessed combo box of [`dropdown`] showing `current`, whose menu keeps a search field and
/// filters in place over a list that scrolls (the font menu and the shortcut import's target
/// picker, #555). `menu` shows the menu with one of [`craft_ui::combo::Menu`]'s header forms and
/// returns what it returned (`None` while the menu is closed). A click inside the menu keeps it
/// open: it closes itself when an option is chosen. A click outside closes it.
pub(crate) fn combo_menu<R>(
ui: &mut Ui,
id: egui::Id,
label: Option<PreviousLabel>,
name: Option<&str>,
id: impl std::hash::Hash,
current: &str,
width: f32,
list: impl FnOnce(&mut Ui) -> Option<R>,
menu: impl FnOnce(craft_ui::combo::Menu<'_>) -> Option<R>,
) -> Option<R> {
let popup = id.with("popup");
let open = egui::Popup::is_id_open(ui.ctx(), popup);
let t = Tokens::get(ui.ctx());
let label = take_previous_label(ui);
egui::Frame::NONE
.fill(t.input)
.stroke(Stroke::new(1.0, t.input_border))
.corner_radius(CornerRadius::same(3))
.show(ui, |ui| {
let resp = combo_field(ui, id_with(ui.scope_id(), id), label, None, current, width - 4.0);
menu(craft_ui::combo::Menu::new(ui, &resp).close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside))
})
.inner
}
/// The field of a searchable combo box, drawn as egui's combo box button, with the id `id`: its
/// menu is the popup `id.with("popup")`.
fn combo_field(ui: &mut Ui, id: egui::Id, label: Option<PreviousLabel>, name: Option<&str>, current: &str, width: f32) -> Response {
let open = egui::Popup::is_id_open(ui.ctx(), id.with("popup"));
let pad = ui.spacing().button_padding;
let icon = Vec2::splat(ui.spacing().icon_width);
let galley = egui::WidgetText::from(egui::RichText::new(current).size(12.0)).into_galley(
@@ -155,7 +158,10 @@ fn searchable_combo<R>(
egui::TextStyle::Button,
);
let height = (galley.size().y.max(icon.y) + 2.0 * pad.y).max(ui.spacing().interact_size.y);
let (rect, resp) = ui.allocate_exact_size(vec2(width, height), Sense::click());
let size = vec2(width, height);
let (_, space) = ui.allocate_space(size);
let rect = egui::Align2::CENTER_CENTER.align_size_within_rect(size, space);
let resp = ui.interact(rect, id, Sense::click());
// Screen readers get what egui's combo box gives them: its role and the text it shows.
resp.widget_info(|| egui::WidgetInfo {
enabled: ui.is_enabled(),
@@ -177,26 +183,7 @@ fn searchable_combo<R>(
Stroke::NONE,
));
}
// It opens below the button, or above it when there is more room there, and the list gets
// the room left (the popup's frame aside).
const GAP: f32 = 12.0;
let screen = ui.ctx().content_rect();
let frame = ui.spacing().menu_margin.sum().y + GAP;
let (below, above) = (screen.bottom() - rect.bottom() - frame, rect.top() - screen.top() - frame);
let (align, room) = if below >= above { (egui::RectAlign::BOTTOM_START, below) } else { (egui::RectAlign::TOP_START, above) };
egui::Popup::menu(&resp)
.id(popup)
.width(rect.width())
.align(align)
.align_alternatives(&[])
.close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside)
.show(|ui| {
ui.set_min_width(ui.available_width());
ui.set_max_height(room);
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
list(ui)
})
.and_then(|r| r.inner)
resp
}
/// A popover anchored to `resp`, opened and closed by `toggle` (usually `resp.clicked()`), and
+1 -1
View File
@@ -324,7 +324,7 @@ fn a_combos_keyboard_guard_reads_the_combos_own_button_id() {
// `ComboBox::from_id_salt` gives the button: they must be equal, or the guard
// reads and gives back the keyboard of nothing.
let expected = combo_button_id(ui, &"combo-guard-test");
let response = egui::ComboBox::from_id_salt("combo-guard-test").selected_text("One").show_ui(ui, |_| ());
let response = craft_ui::ComboBox::from_id_salt("combo-guard-test").selected_text("One").show_ui(ui, |_| ());
seen.set(expected == response.response.id);
});
h.run_steps(2);
+1 -1
View File
@@ -1,7 +1,7 @@
# PDF backend
PDF writing uses `craft-pdf` from `storytold/craft-libs`, pinned to
`a0916808f7d78bed8c7b90cdf937176015454a7f`. Its `write` feature supplies Krilla configuration,
`92d392cff132eaf53e13afff77f38b5c11b73aa0`. Its `write` feature supplies Krilla configuration,
metadata construction, fallible font preparation and serialization, and native unwind guards.
The dependency disables default features and enables `write` and `interpret`.
The production writer retains Krilla’s raster-image support without its optional text shaper.
+1 -1
View File
@@ -5,7 +5,7 @@ use std::process::Command;
use serde_json::Value;
const REV: &str = "a0916808f7d78bed8c7b90cdf937176015454a7f";
const REV: &str = "92d392cff132eaf53e13afff77f38b5c11b73aa0";
const PACKAGES: &[(&str, &str)] = &[
("craft-pdf", "0.1.0"),
("krilla", "0.8.2"),