fix(ui): dropdown menus grow back after a search with no matches (#2511)

* fix(ui-egui): craft_ui::ComboBox for every combo box, so open menus grow back (egui#5225)

Repins craft-libs to aiongg/craft-libs 60d60d48 (storytold main with the shared
dropdown) and bans egui::ComboBox in clippy.toml.

* fix(ui-egui): font menu under the font field is a craft_ui combo menu that grows back

The filter row is the menu's header and only the font list scrolls, so a search
that matched nothing grows the menu back when it is cleared.

* 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:27:52 -04:00
committed by GitHub
parent 01c904b041
commit bc4b48f67d
22 changed files with 387 additions and 164 deletions
Generated
+191 -6
View File
@@ -860,6 +860,15 @@ dependencies = [
"error-code",
]
[[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"
@@ -1038,12 +1047,40 @@ dependencies = [
[[package]]
name = "craft-docpath"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=512b54355cd1418f4e8c56a3fbd7483c3c274bf3#512b54355cd1418f4e8c56a3fbd7483c3c274bf3"
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-pdf"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=512b54355cd1418f4e8c56a3fbd7483c3c274bf3#512b54355cd1418f4e8c56a3fbd7483c3c274bf3"
source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0"
dependencies = [
"hayro-syntax",
"thiserror 2.0.21",
@@ -1052,20 +1089,28 @@ dependencies = [
[[package]]
name = "craft-ui"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=512b54355cd1418f4e8c56a3fbd7483c3c274bf3#512b54355cd1418f4e8c56a3fbd7483c3c274bf3"
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",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "craft-update"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=512b54355cd1418f4e8c56a3fbd7483c3c274bf3#512b54355cd1418f4e8c56a3fbd7483c3c274bf3"
source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0"
dependencies = [
"chrono",
"dunce",
@@ -2446,6 +2491,17 @@ version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484"
[[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"
@@ -2870,7 +2926,7 @@ checksum = "9f4d0e94ddd48749f06bbe4e5389fb9799a0c45bcaf00495042076ef05e3241a"
[[package]]
name = "hayro-jbig2"
version = "0.3.0"
source = "git+https://github.com/storytold/craft-libs?rev=512b54355cd1418f4e8c56a3fbd7483c3c274bf3#512b54355cd1418f4e8c56a3fbd7483c3c274bf3"
source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0"
dependencies = [
"hayro-ccitt",
]
@@ -2884,7 +2940,7 @@ checksum = "c75ab947623ef4ccaa7acf0579edf7cbb5a73838e3839a7be73335e522f433a1"
[[package]]
name = "hayro-syntax"
version = "0.7.2"
source = "git+https://github.com/storytold/craft-libs?rev=512b54355cd1418f4e8c56a3fbd7483c3c274bf3#512b54355cd1418f4e8c56a3fbd7483c3c274bf3"
source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0"
dependencies = [
"hayro-jbig2",
"hayro-jpeg2000",
@@ -2942,6 +2998,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"
@@ -2950,11 +3023,32 @@ checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb"
dependencies = [
"displaydoc",
"litemap",
"serde",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_locale_fallback"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "251af8e57c9400e3eb58242fe5b8b1152b2a64fdf4cf632f923c38ccee6f2fa9"
dependencies = [
"icu_locale_core",
"icu_locale_fallback_data",
"icu_provider",
"potential_utf",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_locale_fallback_data"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "decf2a22ec8fa68f1a0c1129a3f8583f8f8bc24e8b9ccbe98ead99f62a4dc3a8"
[[package]]
name = "icu_normalizer"
version = "2.3.0"
@@ -2967,6 +3061,7 @@ dependencies = [
"icu_provider",
"smallvec",
"utf16_iter",
"utf8_iter",
"write16",
"zerovec",
]
@@ -2977,6 +3072,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"
@@ -3006,6 +3127,9 @@ checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73"
dependencies = [
"displaydoc",
"icu_locale_core",
"postcard",
"serde",
"stable_deref_trait",
"writeable",
"yoke",
"zerofrom",
@@ -3013,6 +3137,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 = "idna"
version = "1.1.0"
@@ -3328,6 +3476,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"
@@ -4320,12 +4471,24 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661"
dependencies = [
"serde_core",
"writeable",
"zerovec",
]
@@ -5406,7 +5569,10 @@ 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]]
@@ -5578,6 +5744,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643"
dependencies = [
"displaydoc",
"serde_core",
"zerovec",
]
@@ -6623,6 +6790,17 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-registry"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
dependencies = [
"windows-link",
"windows-result 0.4.1",
"windows-strings",
]
[[package]]
name = "windows-result"
version = "0.1.2"
@@ -6936,6 +7114,9 @@ name = "writeable"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
dependencies = [
"either",
]
[[package]]
name = "x11-dl"
@@ -7192,8 +7373,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f"
dependencies = [
"displaydoc",
"litemap",
"serde_core",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
@@ -7202,6 +7386,7 @@ version = "0.11.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8"
dependencies = [
"serde",
"yoke",
"zerofrom",
"zerovec-derive",
+6 -6
View File
@@ -11,9 +11,9 @@ rust-version = "1.95"
repository = "https://github.com/storytold/effectcraft"
[workspace.dependencies]
craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "512b54355cd1418f4e8c56a3fbd7483c3c274bf3", version = "=0.1.0", default-features = false, features = ["read"] }
craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "512b54355cd1418f4e8c56a3fbd7483c3c274bf3" }
craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "512b54355cd1418f4e8c56a3fbd7483c3c274bf3", features = ["egui"] }
craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", version = "=0.1.0", default-features = false, features = ["read"] }
craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0" }
craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", features = ["egui"] }
# internal
effectcraft-time = { path = "crates/time" }
effectcraft-geom = { path = "crates/geom" }
@@ -108,7 +108,7 @@ pollster = "1"
# ui
# Shared frontend components; keep this dependency above the engine boundary.
# Shared UI and the complete egui family select reviewed immutable commits below.
craft-ui = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "512b54355cd1418f4e8c56a3fbd7483c3c274bf3", features = ["serde"] }
craft-ui = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", features = ["serde"] }
# `default_fonts` and `monochrome_emoji_fonts` are explicit: the app's `FontDefinitions::default()`
# and its `✔`/`▣` chrome glyphs need both, and kittest's own dev-dependencies do not propagate.
egui = { version = "0.37", default-features = false, features = ["default_fonts", "monochrome_emoji_fonts"] }
@@ -220,8 +220,8 @@ lto = "thin"
codegen-units = 1
[patch.crates-io]
hayro-syntax = { git = "https://github.com/storytold/craft-libs", rev = "512b54355cd1418f4e8c56a3fbd7483c3c274bf3", version = "=0.7.2" }
hayro-jbig2 = { git = "https://github.com/storytold/craft-libs", rev = "512b54355cd1418f4e8c56a3fbd7483c3c274bf3", version = "=0.3.0" }
hayro-syntax = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", version = "=0.7.2" }
hayro-jbig2 = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", version = "=0.3.0" }
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" }
+3
View File
@@ -2,3 +2,6 @@ allow-unwrap-in-tests = true
allow-expect-in-tests = true
allow-panic-in-tests = true
allow-indexing-slicing-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)" },
]
+1 -1
View File
@@ -138,7 +138,7 @@ ffmpeg elsewhere) to check the code tables against an independent implementation
## Shared parser setup and bounds
The workspace pins `craft-pdf` to `craft-libs` Git revision
`512b54355cd1418f4e8c56a3fbd7483c3c274bf3`, with `default-features = false` and only the explicit `read`
`92d392cff132eaf53e13afff77f38b5c11b73aa0`, with `default-features = false` and only the explicit `read`
feature. This excludes the backend's bundled fonts and Adobe CMaps and preserves EffectCraft's
font and asset policies. The shared parser does not change the supported drawing operators,
image codecs, continuously rasterized SVG tree, editable shapes, or EPS interpreter.
+1 -1
View File
@@ -254,7 +254,7 @@ pub fn contributors_ui(ui: &mut egui::Ui, auto: &mut Registry) {
}
ui.separator();
ui.label(tr("Sort"));
let combo = egui::ComboBox::from_id_salt("credits_sort").selected_text(v.key.label().0).show_ui(ui, |ui| {
let combo = craft_ui::ComboBox::from_id_salt("credits_sort").selected_text(v.key.label().0).show_ui(ui, |ui| {
for k in SortKey::ALL {
let r = ui.selectable_label(v.key == k, k.label().0);
auto.add(&format!("about.credits.sort.{}", k.id()), r.rect, k.label().0);
+5 -5
View File
@@ -286,7 +286,7 @@ pub fn show(
.or_else(|| PRESETS.iter().find(|(_, w, h, par, f)| matches(d, *w, *h, *par, *f)).map(|p| p.0))
.unwrap_or("Custom");
ui.horizontal(|ui| {
let r = egui::ComboBox::from_id_salt("comp-preset").width(300.0).selected_text(current).show_ui(ui, |ui| {
let r = craft_ui::ComboBox::from_id_salt("comp-preset").width(300.0).selected_text(current).show_ui(ui, |ui| {
// Your own presets first, then the built-in ones.
for q in custom {
if ui.selectable_label(q.name == current, &q.name).clicked() {
@@ -350,7 +350,7 @@ pub fn show(
.find(|(_, v)| (v - d.pixel_aspect).abs() < 1e-3)
.map(|p| p.0.to_string())
.unwrap_or_else(|| format!("{:.2}", d.pixel_aspect));
let r = egui::ComboBox::from_id_salt("comp-par").width(220.0).selected_text(cur).show_ui(ui, |ui| {
let r = craft_ui::ComboBox::from_id_salt("comp-par").width(220.0).selected_text(cur).show_ui(ui, |ui| {
for (name, v) in PIXEL_ASPECTS {
if ui.selectable_label((v - d.pixel_aspect).abs() < 1e-3, *name).clicked() {
d.pixel_aspect = *v;
@@ -369,7 +369,7 @@ pub fn show(
// Any rate can be typed; the list holds the common ones.
let r = ui.add(crate::widgets::drag_value(egui::DragValue::new(&mut d.fps).range(1.0..=999.0).speed(0.01).max_decimals(3)));
auto.add("dialog.comp.frameRate", r.rect, "Frame Rate");
let r = egui::ComboBox::from_id_salt("comp-fps").width(18.0).selected_text("").show_ui(ui, |ui| {
let r = craft_ui::ComboBox::from_id_salt("comp-fps").width(18.0).selected_text("").show_ui(ui, |ui| {
for f in FRAME_RATES {
if ui.selectable_label((d.fps - f).abs() < 1e-3, format!("{f}")).clicked() {
d.fps = *f;
@@ -387,7 +387,7 @@ pub fn show(
// the frame it renders beside it.
ui.label(tr("Resolution:"));
ui.horizontal(|ui| {
let r = egui::ComboBox::from_id_salt("comp-resolution").width(90.0).selected_text(tr(d.resolution.label())).show_ui(ui, |ui| {
let r = craft_ui::ComboBox::from_id_salt("comp-resolution").width(90.0).selected_text(tr(d.resolution.label())).show_ui(ui, |ui| {
for res in Resolution::ALL {
ui.selectable_value(&mut d.resolution, res, tr(res.label()));
}
@@ -474,7 +474,7 @@ pub fn show(
Tab::Renderer => {
ui.horizontal(|ui| {
ui.label(tr("Renderer:"));
let r = egui::ComboBox::from_id_salt("comp-renderer").selected_text(tr(if d.advanced_3d { "Advanced 3D" } else { "Classic 3D" })).show_ui(
let r = craft_ui::ComboBox::from_id_salt("comp-renderer").selected_text(tr(if d.advanced_3d { "Advanced 3D" } else { "Classic 3D" })).show_ui(
ui,
|ui| {
ui.selectable_value(&mut d.advanced_3d, false, tr("Classic 3D"));
+1 -1
View File
@@ -536,7 +536,7 @@ fn solid(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
.find(|(_, v)| (v - par).abs() < 1e-3)
.map(|p| p.0.to_string())
.unwrap_or_else(|| format!("{par:.2}"));
let r = egui::ComboBox::from_id_salt("solid-par").width(240.0).selected_text(cur).show_ui(ui, |ui| {
let r = craft_ui::ComboBox::from_id_salt("solid-par").width(240.0).selected_text(cur).show_ui(ui, |ui| {
for (label, v) in super::comp_settings::PIXEL_ASPECTS {
if ui.selectable_label((v - par).abs() < 1e-3, *label).clicked() {
par = *v;
+4 -4
View File
@@ -172,7 +172,7 @@ pub fn camera(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
super::dialogs::modal(ctx, "Camera Settings", vec2(520.0, 420.0), t, |ui| {
egui::Grid::new("cam-grid").num_columns(2).spacing([14.0, 9.0]).show(ui, |ui| {
ui.label(tr("Type"));
egui::ComboBox::from_id_salt("cam-type").selected_text(if d.two_node { "Two-Node Camera" } else { "One-Node Camera" }).show_ui(ui, |ui| {
craft_ui::ComboBox::from_id_salt("cam-type").selected_text(if d.two_node { "Two-Node Camera" } else { "One-Node Camera" }).show_ui(ui, |ui| {
ui.selectable_value(&mut d.two_node, true, tr("Two-Node Camera"));
ui.selectable_value(&mut d.two_node, false, tr("One-Node Camera"));
});
@@ -184,7 +184,7 @@ pub fn camera(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
ui.label(tr("Preset"));
let focal = focal_for_zoom(w, d.zoom);
let cur = PRESETS.iter().find(|(_, f)| (f - focal).abs() < 1e-6).map(|(n, _)| *n).unwrap_or("Custom");
egui::ComboBox::from_id_salt("cam-preset").selected_text(cur).show_ui(ui, |ui| {
craft_ui::ComboBox::from_id_salt("cam-preset").selected_text(cur).show_ui(ui, |ui| {
for (n, f) in PRESETS {
if ui.selectable_label(cur == n, n).clicked() {
d.zoom = zoom_for_focal(w, f);
@@ -268,7 +268,7 @@ pub fn light(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
crate::widgets::focus_on_open(ui.ctx(), &r, &d.name);
ui.end_row();
ui.label(tr("Light Type"));
let combo = egui::ComboBox::from_id_salt("light-type").selected_text(tr(d.kind.label())).show_ui(ui, |ui| {
let combo = craft_ui::ComboBox::from_id_salt("light-type").selected_text(tr(d.kind.label())).show_ui(ui, |ui| {
for (i, k) in LightKind::ALL.into_iter().enumerate() {
let item = ui.selectable_value(&mut d.kind, k, tr(k.label()));
app.auto.add(&format!("dialog.light.typeItem.{i}"), item.rect, k.label());
@@ -292,7 +292,7 @@ pub fn light(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
}
if falls_off(d.kind) {
ui.label(tr("Falloff"));
egui::ComboBox::from_id_salt("light-falloff").selected_text(FALLOFFS[d.falloff.min(2)]).show_ui(ui, |ui| {
craft_ui::ComboBox::from_id_salt("light-falloff").selected_text(FALLOFFS[d.falloff.min(2)]).show_ui(ui, |ui| {
for (i, f) in FALLOFFS.iter().enumerate() {
ui.selectable_value(&mut d.falloff, i, *f);
}
+5 -5
View File
@@ -76,7 +76,7 @@ fn body(app: &mut EffectcraftApp, ui: &mut egui::Ui) -> (Actions, Actions) {
// ---- Primary + Solo.
ui.horizontal(|ui| {
ui.label(RichText::new(tr("Primary:")).color(t.text_dim));
let r = egui::ComboBox::from_id_salt("eg-primary").selected_text(&comp_name).width(150.0).show_ui(ui, |ui| {
let r = craft_ui::ComboBox::from_id_salt("eg-primary").selected_text(&comp_name).width(150.0).show_ui(ui, |ui| {
for (id, _) in project.comps() {
let name = project.item(*id).map(|i| i.name.clone()).unwrap_or_default();
if ui.selectable_label(*id == cid, name).clicked() {
@@ -374,7 +374,7 @@ fn value_widget(app: &mut EffectcraftApp, ui: &mut egui::Ui, auto: &str, ty: Opt
_ => vec![],
};
let cur = v.as_enum() as usize;
let r = egui::ComboBox::from_id_salt(("eg-dd", auto)).selected_text(opts.get(cur).cloned().unwrap_or_default()).show_ui(ui, |ui| {
let r = craft_ui::ComboBox::from_id_salt(("eg-dd", auto)).selected_text(opts.get(cur).cloned().unwrap_or_default()).show_ui(ui, |ui| {
for (i, o) in opts.iter().enumerate() {
if ui.selectable_label(i == cur, o).clicked() {
out = Some(json!(i));
@@ -437,7 +437,7 @@ fn value_widget(app: &mut EffectcraftApp, ui: &mut egui::Ui, auto: &str, ty: Opt
let fams = effectcraft_engine::text::fonts::families();
let mut d = (**doc).clone();
let mut changed = false;
let r1 = egui::ComboBox::from_id_salt(("eg-font", auto)).selected_text(&d.font).width(110.0).show_ui(ui, |ui| {
let r1 = craft_ui::ComboBox::from_id_salt(("eg-font", auto)).selected_text(&d.font).width(110.0).show_ui(ui, |ui| {
for (f, _) in &fams {
if ui.selectable_label(*f == d.font, f).clicked() {
d.font = f.clone();
@@ -446,7 +446,7 @@ fn value_widget(app: &mut EffectcraftApp, ui: &mut egui::Ui, auto: &str, ty: Opt
}
});
let styles: Vec<String> = fams.iter().find(|(f, _)| *f == d.font).map(|(_, st)| st.clone()).unwrap_or_default();
let r2 = egui::ComboBox::from_id_salt(("eg-style", auto)).selected_text(&d.style).width(70.0).show_ui(ui, |ui| {
let r2 = craft_ui::ComboBox::from_id_salt(("eg-style", auto)).selected_text(&d.style).width(70.0).show_ui(ui, |ui| {
for st in &styles {
if ui.selectable_label(*st == d.style, st).clicked() {
d.style = st.clone();
@@ -532,7 +532,7 @@ fn instance(app: &mut EffectcraftApp, ui: &mut egui::Ui, actions: &mut Actions)
.filter(|i| matches!(&i.kind, ItemKind::Footage(f) if f.has_video))
.map(|i| (i.id, i.name.clone()))
.collect();
let r = egui::ComboBox::from_id_salt(("eg-media", layer.id.0, c.id)).selected_text(cur_name).show_ui(ui, |ui| {
let r = craft_ui::ComboBox::from_id_salt(("eg-media", layer.id.0, c.id)).selected_text(cur_name).show_ui(ui, |ui| {
for (id, name) in &items {
if ui.selectable_label(*id == cur, name).clicked() {
actions.push(("essential.set".into(), json!({"layer": layer.id.0, "control": c.id, "item": id.0})));
+1 -1
View File
@@ -830,7 +830,7 @@ fn form_grid(ui: &mut egui::Ui, f: &mut Form, regs: &mut Vec<(String, egui::Rect
let cur = options.get(*sel).map(|o| o.0.clone()).unwrap_or_default();
// The column keeps its width: a long choice (a GPU adapter's name) ends in an
// ellipsis instead of widening the form past the dialog (#978).
egui::ComboBox::from_id_salt(("form", fl.key.as_str()))
craft_ui::ComboBox::from_id_salt(("form", fl.key.as_str()))
.selected_text(tr(&cur))
.width(240.0)
.truncate()
+1 -1
View File
@@ -191,7 +191,7 @@ pub fn velocity(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
fn combo(ui: &mut egui::Ui, app: &mut EffectcraftApp, id: &str, sel: &mut usize, opts: &[&str], enabled: bool) {
ui.add_enabled_ui(enabled, |ui| {
// The options are the English source strings; the automation id and label stay English.
let r = egui::ComboBox::from_id_salt(id).width(200.0).selected_text(tr(opts[*sel])).show_ui(ui, |ui| {
let r = craft_ui::ComboBox::from_id_salt(id).width(200.0).selected_text(tr(opts[*sel])).show_ui(ui, |ui| {
for (i, o) in opts.iter().enumerate() {
ui.selectable_value(sel, i, tr(o));
}
@@ -91,7 +91,7 @@ fn control(ui: &mut egui::Ui, p: &Property, v: &KV) -> Option<Value> {
}
(KV::Enum(i), ParamUi::Popup { options }) => {
let mut sel = *i as usize;
egui::ComboBox::from_id_salt(("ls-popup", p.uid)).selected_text(options.get(sel).cloned().unwrap_or_default()).show_ui(ui, |ui| {
craft_ui::ComboBox::from_id_salt(("ls-popup", p.uid)).selected_text(options.get(sel).cloned().unwrap_or_default()).show_ui(ui, |ui| {
for (k, o) in options.iter().enumerate() {
ui.selectable_value(&mut sel, k, o);
}
+1 -1
View File
@@ -197,7 +197,7 @@ pub fn dialog(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
ui.label(tr("Label:"));
let labels = effectcraft_engine::color::Label::ALL;
let cur = labels.get(d.label).copied().unwrap_or_default();
let r = egui::ComboBox::from_id_salt("dialog.marker.label").selected_text(app.session.prefs.label_name(cur)).show_ui(ui, |ui| {
let r = craft_ui::ComboBox::from_id_salt("dialog.marker.label").selected_text(app.session.prefs.label_name(cur)).show_ui(ui, |ui| {
for (i, l) in labels.into_iter().enumerate() {
if crate::widgets::label_entry(ui, l, &app.session.prefs.label_name(l), d.label == i, &app.tokens).clicked() {
d.label = i;
@@ -58,7 +58,7 @@ pub fn dialog(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
super::dialogs::modal(ctx, "New 3D Primitive", vec2(420.0, 300.0), t, |ui| {
egui::Grid::new("primitive-grid").num_columns(2).spacing([14.0, 9.0]).show(ui, |ui| {
ui.label(tr("Type"));
let combo = egui::ComboBox::from_id_salt("primitive-type").selected_text(tr(d.kind.label())).show_ui(ui, |ui| {
let combo = craft_ui::ComboBox::from_id_salt("primitive-type").selected_text(tr(d.kind.label())).show_ui(ui, |ui| {
for (i, k) in PrimitiveKind::ALL.into_iter().enumerate() {
let item = ui.selectable_value(&mut d.kind, k, tr(k.label()));
app.auto.add(&format!("dialog.primitive.typeItem.{i}"), item.rect, k.label());
+2 -2
View File
@@ -60,7 +60,7 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
ui.label(slot.label());
let cur = templates.default_name(k, slot);
let mut pick = cur.clone();
let r = egui::ComboBox::from_id_salt(("tpl-def", slot.label())).selected_text(&cur).width(300.0).show_ui(ui, |ui| {
let r = craft_ui::ComboBox::from_id_salt(("tpl-def", slot.label())).selected_text(&cur).width(300.0).show_ui(ui, |ui| {
for n in &list {
ui.selectable_value(&mut pick, n.clone(), n);
}
@@ -77,7 +77,7 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
ui.label(egui::RichText::new(tr("Settings")).font(Tokens::semibold(12.5)).color(t.text));
ui.horizontal(|ui| {
ui.label(tr("Settings Name:"));
let r = egui::ComboBox::from_id_salt("tpl-select").selected_text(&st.selected).width(300.0).show_ui(ui, |ui| {
let r = craft_ui::ComboBox::from_id_salt("tpl-select").selected_text(&st.selected).width(300.0).show_ui(ui, |ui| {
for n in &list {
ui.selectable_value(&mut st.selected, n.clone(), n);
}
+1 -1
View File
@@ -214,7 +214,7 @@ fn draw_widget(app: &mut EffectcraftApp, ui: &mut egui::Ui, origin: egui::Pos2,
let mut pick: Option<usize> = None;
ui.add_enabled_ui(w.enabled, |ui| {
ui.scope_builder(UiBuilder::new().max_rect(rect), |ui| {
egui::ComboBox::from_id_salt(id).selected_text(cur).width(rect.width() - 8.0).show_ui(ui, |ui| {
craft_ui::ComboBox::from_id_salt(id).selected_text(cur).width(rect.width() - 8.0).show_ui(ui, |ui| {
for (i, it) in w.items.iter().enumerate() {
if it == "-" {
ui.separator();
+3 -3
View File
@@ -78,7 +78,7 @@ fn page_ui(app: &mut EffectcraftApp, ui: &mut egui::Ui, page: &Page, cur: &Value
ui.horizontal(|ui| {
ui.add_sized(vec2(label_w, 18.0), egui::Label::new(tr("Default Output:")));
let shown = if sel.is_empty() { tr("System Default").to_string() } else { sel.clone() };
let r = egui::ComboBox::from_id_salt(("settings", key)).selected_text(shown).width(260.0).show_ui(ui, |ui| {
let r = craft_ui::ComboBox::from_id_salt(("settings", key)).selected_text(shown).width(260.0).show_ui(ui, |ui| {
if ui.selectable_label(sel.is_empty(), tr("System Default")).clicked() {
acts.push(Act::Set(key.into(), json!("")));
}
@@ -157,7 +157,7 @@ fn page_ui(app: &mut EffectcraftApp, ui: &mut egui::Ui, page: &Page, cur: &Value
Kind::Choice(opts) => {
let curv = v.as_str().map_or_else(|| v.to_string(), str::to_string);
let shown = opts.iter().find(|o| o.1 == curv).map(|o| o.0).unwrap_or(curv.as_str()).to_string();
egui::ComboBox::from_id_salt(("settings", key))
craft_ui::ComboBox::from_id_salt(("settings", key))
.selected_text(tr(&shown))
.width(220.0)
.show_ui(ui, |ui| {
@@ -378,7 +378,7 @@ fn appearance_ui(app: &mut EffectcraftApp, ui: &mut egui::Ui, cur: &Value, t: &T
ui.horizontal(|ui| {
ui.add_sized(vec2(250.0, 18.0), egui::Label::new(format!("{}:", tr("Appearance Mode"))).truncate());
let shown = APPEARANCE_MODES.iter().find(|(_, v)| *v == mode).map_or(mode.as_str(), |(l, _)| *l);
let r = egui::ComboBox::from_id_salt(("settings", "appearance.appearanceMode"))
let r = craft_ui::ComboBox::from_id_salt(("settings", "appearance.appearanceMode"))
.selected_text(tr(shown))
.width(220.0)
.show_ui(ui, |ui| {
+1 -1
View File
@@ -264,7 +264,7 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
// ---- preset bar
ui.horizontal(|ui| {
ui.label(tr("Preset:"));
let r = egui::ComboBox::from_id_salt("sc-preset").selected_text(&active).width(220.0).show_ui(ui, |ui| {
let r = craft_ui::ComboBox::from_id_salt("sc-preset").selected_text(&active).width(220.0).show_ui(ui, |ui| {
for n in &presets {
if ui.selectable_label(*n == active, n).clicked() {
run.push(("shortcuts.preset".into(), json!({"op": "select", "name": n})));
+120 -119
View File
@@ -635,130 +635,131 @@ pub(super) fn font_picker(app: &mut EffectcraftApp, ui: &mut egui::Ui, id: egui:
return None;
}
app.auto.add(&format!("{auto}.toggle"), arrow, "Close font list");
let mut chosen = None;
let screen = ui.ctx().content_rect();
let below = screen.bottom() - rect.bottom() - 16.0;
let above = below < 100.0 && rect.top() - screen.top() > below;
let max_height = (if above { rect.top() - screen.top() - 16.0 } else { below }).clamp(28.0, 380.0);
let anchor = if above { rect.left_top() } else { rect.left_bottom() };
let pivot = if above { Align2::LEFT_BOTTOM } else { Align2::LEFT_TOP };
widgets::note_open_popup_list(ui.ctx());
let area = egui::Area::new(id.with("area")).order(egui::Order::Foreground).pivot(pivot).fixed_pos(anchor).show(ui.ctx(), |ui| {
egui::Frame::popup(ui.style()).show(ui, |ui| {
ui.set_width(rect.width().max(180.0));
// Filter row: a checkbox that keeps only the variable fonts.
let filter_h = 22.0;
let row = ui.add_sized([ui.available_width(), filter_h], egui::Button::selectable(false, ""));
let box_rect = Rect::from_min_size(row.rect.min, vec2(18.0, filter_h));
let label_rect = Rect::from_min_max(pos2(box_rect.max.x + 2.0, row.rect.min.y), row.rect.max);
let boxed = widgets::checkbox(ui, box_rect, only_variable, &t, id.with("variable-check"));
ui.painter().text(label_rect.left_center(), Align2::LEFT_CENTER, tr("Variable fonts only"), Tokens::ui(11.5), t.text);
app.auto.add(&format!("{auto}.variable"), row.rect, "Variable fonts only");
let toggled = row.clicked() || boxed.clicked();
if toggled {
only_variable = !only_variable;
ui.data_mut(|d| d.insert_temp(variable_id, only_variable));
// Typing goes on into the search field.
ui.memory_mut(|m| m.request_focus(search_id));
let changed = search.changed();
let mut open = true;
// The search lives in the field; the menu's header is the filter row, its list the fonts.
let menu = craft_ui::combo::Menu::new(ui, &search)
.open(&mut open)
.height(380.0)
.close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside)
.style(egui::style::StyleModifier::default());
let app_ref = &mut *app;
let header = |ui: &mut egui::Ui| {
ui.set_width(rect.width().max(180.0));
// Filter row: a checkbox that keeps only the variable fonts.
let filter_h = 22.0;
let row = ui.add_sized([ui.available_width(), filter_h], egui::Button::selectable(false, ""));
let box_rect = Rect::from_min_size(row.rect.min, vec2(18.0, filter_h));
let label_rect = Rect::from_min_max(pos2(box_rect.max.x + 2.0, row.rect.min.y), row.rect.max);
let boxed = widgets::checkbox(ui, box_rect, only_variable, &t, id.with("variable-check"));
ui.painter().text(label_rect.left_center(), Align2::LEFT_CENTER, tr("Variable fonts only"), Tokens::ui(11.5), t.text);
app_ref.auto.add(&format!("{auto}.variable"), row.rect, "Variable fonts only");
let toggled = row.clicked() || boxed.clicked();
if toggled {
only_variable = !only_variable;
ui.data_mut(|d| d.insert_temp(variable_id, only_variable));
// Typing goes on into the search field.
ui.memory_mut(|m| m.request_focus(search_id));
}
let query = state.query.trim().to_lowercase();
let mut seen = std::collections::HashSet::new();
let filtered: Vec<_> = rows
.iter()
.filter(|r| {
!r.family.is_empty()
&& (!only_variable || r.variable)
&& (query.is_empty() || ((r.family.to_lowercase().contains(&query) || r.display.to_lowercase().contains(&query)) && seen.insert(&r.family)))
})
.collect();
if fresh {
state.selected = filtered.iter().position(|r| r.family == current).unwrap_or(0);
}
if changed || toggled {
state.selected = 0;
}
let last = filtered.len().saturating_sub(1);
state.selected = state.selected.min(last);
if down {
state.selected = state.selected.saturating_add(1).min(last);
}
if up {
state.selected = state.selected.saturating_sub(1);
}
let entered = if enter { filtered.get(state.selected).map(|r| r.family.clone()) } else { None };
(app_ref, filtered, query.is_empty(), state.selected, changed || toggled, entered)
};
let list = |ui: &mut egui::Ui,
(app, filtered, all, selected, restart, entered): (
&mut EffectcraftApp,
Vec<&effectcraft_engine::FontRow>,
bool,
usize,
bool,
Option<String>,
)| {
if restart {
ui.scroll_to_cursor(Some(egui::Align::TOP));
}
let mut chosen = entered;
if filtered.is_empty() {
let r = ui.label(tr("No matching fonts"));
app.auto.add(&format!("{auto}.empty"), r.rect, "No matching fonts");
}
let mut was_recent = false;
for (index, r) in filtered.iter().enumerate() {
if all && was_recent && !r.recent {
ui.separator();
}
let query = state.query.trim().to_lowercase();
let mut seen = std::collections::HashSet::new();
let filtered: Vec<_> = rows
.iter()
.filter(|r| {
!r.family.is_empty()
&& (!only_variable || r.variable)
&& (query.is_empty()
|| ((r.family.to_lowercase().contains(&query) || r.display.to_lowercase().contains(&query)) && seen.insert(&r.family)))
})
.collect();
if fresh {
state.selected = filtered.iter().position(|r| r.family == current).unwrap_or(0);
was_recent = r.recent;
let row_width = ui.available_width();
let label_width = if preview { (row_width - 100.0).max(40.0) } else { row_width };
let resp = ui.add_sized([row_width, 24.0], egui::Button::selectable(index == selected, ""));
resp.widget_info(|| egui::WidgetInfo::selected(egui::Role::Button, ui.is_enabled(), index == selected, &r.display));
let label_rect = Rect::from_min_size(resp.rect.min + vec2(5.0, 0.0), vec2(label_width - 5.0, resp.rect.height()));
ui.painter().with_clip_rect(label_rect.intersect(ui.clip_rect())).text(
label_rect.left_center(),
Align2::LEFT_CENTER,
&r.display,
Tokens::ui(12.0),
t.text,
);
if ui.is_rect_visible(resp.rect) {
app.auto.add(&format!("{auto}.option.{}", r.family), resp.rect, &r.display);
}
if search.changed() || toggled {
state.selected = 0;
if index == selected && (fresh || up || down) {
resp.scroll_to_me(None);
}
let last = filtered.len().saturating_sub(1);
state.selected = state.selected.min(last);
if down {
state.selected = state.selected.saturating_add(1).min(last);
}
if up {
state.selected = state.selected.saturating_sub(1);
}
if enter {
chosen = filtered.get(state.selected).map(|r| r.family.clone());
}
// Give the result list its actual height even when the previous query had
// only one row. A cached tiny Area must not collapse a reopened long list.
let filter_room = filter_h + ui.spacing().item_spacing.y;
let height = (filtered.len() as f32 * (24.0 + ui.spacing().item_spacing.y) + 16.0).clamp(28.0, (max_height - filter_room).max(28.0));
// A minimum, from below the filter row (a maximum would move the cursor back up).
ui.set_min_height(height);
let mut scroll = egui::ScrollArea::vertical().id_salt(id.with("results")).max_height(height).min_scrolled_height(height);
if search.changed() || toggled {
scroll = scroll.vertical_scroll_offset(0.0);
}
scroll.show(ui, |ui| {
if filtered.is_empty() {
let r = ui.label(tr("No matching fonts"));
app.auto.add(&format!("{auto}.empty"), r.rect, "No matching fonts");
if preview && ui.is_rect_visible(resp.rect) {
let key = egui::Id::unique(("font-preview", &r.family));
let lines: std::sync::Arc<Vec<Vec<[f32; 2]>>> = match ui.data(|d| d.get_temp(key)) {
Some(l) => l,
None => {
let l = std::sync::Arc::new(effectcraft_engine::font_preview(&r.family, 14.0));
ui.data_mut(|d| d.insert_temp(key, l.clone()));
l
}
};
// Scaled and placed to fit the row's preview column, and clipped to the row (#577).
let room = Rect::from_min_max(pos2(resp.rect.max.x - 95.0, resp.rect.min.y + 1.0), pos2(resp.rect.max.x - 6.0, resp.rect.max.y - 1.0));
if let Some((k, o)) = fit_preview(&lines, room, resp.rect.center().y + 5.0) {
let painter = ui.painter().with_clip_rect(resp.rect.intersect(ui.clip_rect()));
for poly in lines.iter() {
let pts: Vec<egui::Pos2> =
poly.iter().filter(|p| p[0].is_finite() && p[1].is_finite()).map(|p| (o + vec2(p[0], p[1]) * k).to_pos2()).collect();
painter.add(egui::Shape::line(pts, egui::Stroke::new(1.0, t.text_dim)));
}
}
let mut was_recent = false;
for (index, r) in filtered.iter().enumerate() {
if query.is_empty() && was_recent && !r.recent {
ui.separator();
}
was_recent = r.recent;
let row_width = ui.available_width();
let label_width = if preview { (row_width - 100.0).max(40.0) } else { row_width };
let resp = ui.add_sized([row_width, 24.0], egui::Button::selectable(index == state.selected, ""));
resp.widget_info(|| egui::WidgetInfo::selected(egui::Role::Button, ui.is_enabled(), index == state.selected, &r.display));
let label_rect = Rect::from_min_size(resp.rect.min + vec2(5.0, 0.0), vec2(label_width - 5.0, resp.rect.height()));
ui.painter().with_clip_rect(label_rect.intersect(ui.clip_rect())).text(
label_rect.left_center(),
Align2::LEFT_CENTER,
&r.display,
Tokens::ui(12.0),
t.text,
);
if ui.is_rect_visible(resp.rect) {
app.auto.add(&format!("{auto}.option.{}", r.family), resp.rect, &r.display);
}
if index == state.selected && (fresh || up || down) {
resp.scroll_to_me(None);
}
if preview && ui.is_rect_visible(resp.rect) {
let key = egui::Id::unique(("font-preview", &r.family));
let lines: std::sync::Arc<Vec<Vec<[f32; 2]>>> = match ui.data(|d| d.get_temp(key)) {
Some(l) => l,
None => {
let l = std::sync::Arc::new(effectcraft_engine::font_preview(&r.family, 14.0));
ui.data_mut(|d| d.insert_temp(key, l.clone()));
l
}
};
// Scaled and placed to fit the row's preview column, and clipped to the row (#577).
let room = Rect::from_min_max(pos2(resp.rect.max.x - 95.0, resp.rect.min.y + 1.0), pos2(resp.rect.max.x - 6.0, resp.rect.max.y - 1.0));
if let Some((k, o)) = fit_preview(&lines, room, resp.rect.center().y + 5.0) {
let painter = ui.painter().with_clip_rect(resp.rect.intersect(ui.clip_rect()));
for poly in lines.iter() {
let pts: Vec<egui::Pos2> =
poly.iter().filter(|p| p[0].is_finite() && p[1].is_finite()).map(|p| (o + vec2(p[0], p[1]) * k).to_pos2()).collect();
painter.add(egui::Shape::line(pts, egui::Stroke::new(1.0, t.text_dim)));
}
}
}
if resp.clicked() {
chosen = Some(r.family.clone());
}
resp.on_hover_text(&r.display);
}
});
});
});
let outside = widgets::pressed_outside(ui.ctx(), &area.response) && !ui.input(|i| i.pointer.interact_pos().is_some_and(|p| rect.contains(p)));
if chosen.is_some() || outside || escape {
}
if resp.clicked() {
chosen = Some(r.family.clone());
}
resp.on_hover_text(&r.display);
}
chosen
};
let chosen = menu.show_ui_with_header(header, list).flatten();
if chosen.is_some() || !open || escape {
ui.data_mut(|d| {
d.insert_temp(id.with("open"), false);
d.remove::<FontSearch>(state_id);
+3 -3
View File
@@ -537,7 +537,7 @@ pub fn options_dialog(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens)
(LowConfidence::Adapt, "Adapt Feature"),
];
let cur = actions.iter().find(|(a, _)| *a == d.opts.action).map(|(_, l)| *l).unwrap_or("Adapt Feature");
let r = egui::ComboBox::from_id_salt("track-action")
let r = craft_ui::ComboBox::from_id_salt("track-action")
.selected_text(cur)
.show_ui(ui, |ui| {
for (a, l) in actions {
@@ -590,7 +590,7 @@ pub fn target_dialog(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens)
ui.horizontal(|ui| {
ui.label(crate::i18n::tr("Layer:"));
let cur = d.target.and_then(|id| layers.iter().find(|(l, _)| *l == id)).map(|(_, n)| n.clone()).unwrap_or_else(|| "None".into());
let r = egui::ComboBox::from_id_salt("track-target")
let r = craft_ui::ComboBox::from_id_salt("track-target")
.selected_text(cur)
.width(240.0)
.show_ui(ui, |ui| {
@@ -623,7 +623,7 @@ pub fn apply_dialog(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
ui.horizontal(|ui| {
ui.label(crate::i18n::tr("Apply Dimensions:"));
let labels = ["X and Y", "X only", "Y only"];
let r = egui::ComboBox::from_id_salt("track-apply-dims")
let r = craft_ui::ComboBox::from_id_salt("track-apply-dims")
.selected_text(labels[d.dims.min(2)])
.show_ui(ui, |ui| {
for (i, l) in labels.iter().enumerate() {
+34
View File
@@ -176,3 +176,37 @@ fn font_menu_filters_variable_fonts() {
key(&mut h, Key::Escape);
assert_eq!(layer_doc(&h.state().session, LayerId(lid)).unwrap().font, "Inter", "filtering changes nothing");
}
/// The font menu grows back when a query that matched nothing is cleared, while the search
/// field keeps its place and the keyboard; a click on a row then picks that font.
#[test]
fn font_menu_grows_back_after_an_empty_search() {
let mut s = Session::default();
s.execute("comp.new", json!({"width": 320, "height": 180})).unwrap();
let lid = s.execute("layer.newText", json!({"text": "Grow", "font": "Inter"})).unwrap()["layer"].as_u64().unwrap();
let mut h = Harness::builder().with_size(egui::vec2(1600.0, 1000.0)).build_eframe(move |_| EffectcraftApp::new(s));
h.state_mut().show_panel(PanelKind::Character);
h.run_steps(3);
let rows = |h: &Harness<'_, EffectcraftApp>| h.state().auto.query("character.font.option.").len();
let search = |h: &Harness<'_, EffectcraftApp>| h.state().auto.find("character.font.search").map(|e| e.rect);
click(&mut h, "character.font");
h.run_steps(2);
let (full, field) = (rows(&h), search(&h));
assert!(full > 3, "the open menu shows the fonts: {full} rows");
let query = "no_such_font_123456789";
type_text(&mut h, query);
assert_eq!(rows(&h), 0);
assert!(h.state().auto.find("character.font.empty").is_some());
for pressed in query.chars().flat_map(|_| [true, false]) {
h.input_mut().events.push(Event::Key { key: Key::Backspace, physical_key: None, pressed, repeat: false, modifiers: Modifiers::NONE });
}
h.run_steps(3);
// (Within a row: the list scrolls to the current font on opening and to the top when cleared.)
assert!(rows(&h) + 1 >= full, "cleared, the menu shows as many fonts as when it opened: {} of {full}", rows(&h));
assert_eq!(search(&h), field, "the search field stays put");
// The field still has the keyboard: typing filters again.
type_text(&mut h, "JetBrains");
assert!(rows(&h) > 0 && h.state().auto.query("character.font.option.").iter().all(|r| r.label.contains("JetBrains")));
click(&mut h, "character.font.option.JetBrains Mono");
assert_eq!(layer_doc(&h.state().session, LayerId(lid)).unwrap().font, "JetBrains Mono", "a click on a row picks the font");
}
+1 -1
View File
@@ -5,7 +5,7 @@ use std::process::Command;
use serde_json::Value;
const REV: &str = "512b54355cd1418f4e8c56a3fbd7483c3c274bf3";
const REV: &str = "92d392cff132eaf53e13afff77f38b5c11b73aa0";
const PACKAGES: &[(&str, &str)] = &[("craft-pdf", "0.1.0"), ("hayro-syntax", "0.7.2"), ("hayro-jbig2", "0.3.0")];
pub fn run(root: &Path) -> Result<(), String> {