refactor(ui): use craft-ui's ComboBox for every dropdown (#3445)

* ui-egui: use craft_ui::ComboBox for dropdowns; repin craft-libs to 62bb0e1

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

* chore(deps): craft-libs 6af63481 (dropdown fixes)

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

* chore(deps): craft-libs 92d392cf (#387 merged)
This commit is contained in:
aiongg
2026-10-27 10:27:01 -04:00
committed by GitHub
parent 10d626ccc4
commit 70375a3ee8
25 changed files with 78 additions and 68 deletions
Generated
+6 -6
View File
@@ -1061,17 +1061,17 @@ dependencies = [
[[package]]
name = "craft-docpath"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7"
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=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7"
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=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7"
source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0"
dependencies = [
"fixed_decimal",
"icu_decimal",
@@ -1094,7 +1094,7 @@ dependencies = [
[[package]]
name = "craft-media"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7"
source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0"
dependencies = [
"thiserror 2.0.21",
]
@@ -1102,7 +1102,7 @@ dependencies = [
[[package]]
name = "craft-ui"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7"
source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0"
dependencies = [
"craft-fonts-build",
"craft-i18n",
@@ -1123,7 +1123,7 @@ dependencies = [
[[package]]
name = "craft-update"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7"
source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0"
dependencies = [
"chrono",
"dunce",
+4 -4
View File
@@ -72,19 +72,19 @@ chrono = { version = "0.4.45", default-features = false, features = ["std"] }
rayon = "1"
proptest = "1"
roxmltree = "0.21"
craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7" }
craft-media = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7", default-features = false }
craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0" }
craft-media = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", default-features = false }
# ui
# Shared frontend components; keep this dependency above the engine boundary.
craft-ui = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7", features = ["serde", "native-windows"] }
craft-ui = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", features = ["serde", "native-windows"] }
# egui 0.37: bundled fonts are opt-in, and the monochrome symbol/emoji faces moved to their own
# feature. FilmCraft paints ✔ ✓ ✖ ⚡ ⚠ ⚙ ♪ ☑ in panel chrome, so both features are part of the
# production font policy (system_fonts stays off).
egui = { version = "0.37", features = ["default_fonts", "monochrome_emoji_fonts"] }
eframe = { version = "0.37", default-features = false }
egui_kittest = "0.37"
craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7", features = ["egui"] }
craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", features = ["egui"] }
rfd = "0.17"
[workspace.lints.rust]
+4
View File
@@ -2,3 +2,7 @@ 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
@@ -244,7 +244,7 @@ pub fn contributors_ui(ui: &mut egui::Ui, t: &Tokens, auto: &mut Registry) {
}
ui.separator();
ui.label(tl!("Sort"));
let combo = egui::ComboBox::from_id_salt("credits_sort").selected_text(crate::i18n::t(v.key.label().0)).show_ui(ui, |ui| {
let combo = craft_ui::ComboBox::from_id_salt("credits_sort").selected_text(crate::i18n::t(v.key.label().0)).show_ui(ui, |ui| {
for k in SortKey::ALL {
if ui.selectable_label(v.key == k, crate::i18n::t(k.label().0)).clicked() {
v.key = k;
+3 -3
View File
@@ -246,7 +246,7 @@ fn add_tracks(app: &mut FilmcraftApp, ctx: &egui::Context) -> bool {
let list_w = ui.available_width();
// nowhere to choose from while the sequence has no track of the kind
ui.add_enabled_ui(!tracks.is_empty(), |ui| {
let r = egui::ComboBox::from_id_salt(("add-tracks-placement", kind)).selected_text(&shown).width(list_w).show_ui(ui, |ui| {
let r = craft_ui::ComboBox::from_id_salt(("add-tracks-placement", kind)).selected_text(&shown).width(list_w).show_ui(ui, |ui| {
for n in 0..=tracks.len() {
let label = place(n);
let o = ui.selectable_value(after, n, &label);
@@ -261,7 +261,7 @@ fn add_tracks(app: &mut FilmcraftApp, ctx: &egui::Context) -> bool {
label(ui, tl!("Track type"));
let shown = types.iter().find(|(id, _)| *id == chosen.as_str()).map_or(types[0].1, |(_, label)| label).to_string();
let list_w = ui.available_width();
let r = egui::ComboBox::from_id_salt(("add-tracks-type", kind)).selected_text(&shown).width(list_w).show_ui(ui, |ui| {
let r = craft_ui::ComboBox::from_id_salt(("add-tracks-type", kind)).selected_text(&shown).width(list_w).show_ui(ui, |ui| {
for (id, label) in types {
let o = ui.selectable_value(chosen, id.to_string(), *label);
elems.push((format!("addTracks.{kind}.type.option.{id}"), o.rect, label.to_string()));
@@ -355,7 +355,7 @@ fn delete_tracks(app: &mut FilmcraftApp, ctx: &egui::Context) -> bool {
ui.add_space(22.0);
let shown = if target == "empty" { tl!("All Empty Tracks").to_string() } else { target.clone() };
ui.add_enabled_ui(*on, |ui| {
let r = egui::ComboBox::from_id_salt(("delete-tracks", kind)).selected_text(&shown).width(170.0).show_ui(ui, |ui| {
let r = craft_ui::ComboBox::from_id_salt(("delete-tracks", kind)).selected_text(&shown).width(170.0).show_ui(ui, |ui| {
let e = ui.selectable_value(target, "empty".to_string(), tl!("All Empty Tracks"));
elems.push((format!("deleteTracks.{kind}.option.empty"), e.rect, "All Empty Tracks".into()));
for n in list {
+1 -1
View File
@@ -651,7 +651,7 @@ pub(crate) fn param_row(
fit_to_row(&mut vui);
// (narrower when the effect list is narrow, so it stays left of the divider)
let w = vui.available_width().min(130.0);
let combo = egui::ComboBox::from_id_salt(id).selected_text(opts.get(sel).map_or("", |o| crate::i18n::t(o))).width(w).show_ui(&mut vui, |ui| {
let combo = craft_ui::ComboBox::from_id_salt(id).selected_text(opts.get(sel).map_or("", |o| crate::i18n::t(o))).width(w).show_ui(&mut vui, |ui| {
for (i, o) in opts.iter().enumerate() {
if ui.selectable_value(&mut sel, i, crate::i18n::t(o)).changed() {
set = Some(json!(i));
+8 -7
View File
@@ -214,7 +214,7 @@ fn typed_body(ui: &mut egui::Ui, cx: &mut Ctx, st: &EssentialSound, items: &[Tra
let mut draft: Option<String> = ui.data(|d| d.get_temp(draft_id));
ui.horizontal(|ui| {
let w = ui.available_width() - 56.0;
let cb = egui::ComboBox::from_id_salt("es-preset").selected_text(&current).width(w).show_ui(ui, |ui| {
let cb = craft_ui::ComboBox::from_id_salt("es-preset").selected_text(&current).width(w).show_ui(ui, |ui| {
for p in &mine {
if ui.selectable_label(p.name == st.preset, &p.name).clicked() {
picked = Some(p.name.clone());
@@ -563,13 +563,14 @@ fn slider_track(ui: &mut egui::Ui, cx: &mut Ctx, track: Rect, key: &str, v: f64,
fn combo(ui: &mut egui::Ui, cx: &mut Ctx, key: &str, names: &[&str], current: &str, active: bool) {
let mut picked = None;
ui.add_enabled_ui(active, |ui| {
let cb = egui::ComboBox::from_id_salt(("es-combo", key)).selected_text(crate::i18n::t(current)).width(ui.available_width() - 4.0).show_ui(ui, |ui| {
for n in names {
if ui.selectable_label(*n == current, crate::i18n::t(n)).clicked() {
picked = Some(n.to_string());
let cb =
craft_ui::ComboBox::from_id_salt(("es-combo", key)).selected_text(crate::i18n::t(current)).width(ui.available_width() - 4.0).show_ui(ui, |ui| {
for n in names {
if ui.selectable_label(*n == current, crate::i18n::t(n)).clicked() {
picked = Some(n.to_string());
}
}
}
});
});
cx.auto.add(&format!("essentialSound.{key}"), cb.response.rect, key);
});
if let Some(n) = picked {
+1 -1
View File
@@ -67,7 +67,7 @@ pub fn events(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: Rect) {
Level::Warning => tl!("Warnings and Errors"),
Level::Error => tl!("Errors Only"),
};
let r = egui::ComboBox::from_id_salt("events-filter").selected_text(label(min)).width(150.0).show_ui(ui, |ui| {
let r = craft_ui::ComboBox::from_id_salt("events-filter").selected_text(label(min)).width(150.0).show_ui(ui, |ui| {
for l in [Level::Info, Level::Warning, Level::Error] {
let r = ui.selectable_label(min == l, label(l));
elems.push((format!("events.filter.{}", l.label().to_ascii_lowercase()), r.rect, label(l).into()));
@@ -25,7 +25,7 @@ pub(super) fn combo(ui: &mut egui::Ui, reg: &mut Reg, id: &str, selected: &str,
/// [`combo`] whose options carry why they are greyed out (label, `Some(reason)` when disabled).
fn combo_why(ui: &mut egui::Ui, reg: &mut Reg, id: &str, selected: &str, options: &[(String, Option<&str>)], width: f32) -> Option<usize> {
let mut chosen = None;
let r = egui::ComboBox::from_id_salt(id).selected_text(crate::i18n::t(selected)).width(width).show_ui(ui, |ui| {
let r = craft_ui::ComboBox::from_id_salt(id).selected_text(crate::i18n::t(selected)).width(width).show_ui(ui, |ui| {
for (i, (label, why)) in options.iter().enumerate() {
let enabled = why.is_none();
let resp = ui.add_enabled(enabled, egui::Button::selectable(label == selected, crate::i18n::t(label)));
@@ -247,8 +247,9 @@ pub(super) fn settings_column(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: R
});
// Preset: favourites, then every preset by category, then More presets…
row(ui, &t, tl!("Preset"), |ui| {
let r =
egui::ComboBox::from_id_salt("export.preset").selected_text(crate::i18n::t(&ex.preset)).width(ui.available_width() - 4.0).show_ui(ui, |ui| {
let r = craft_ui::ComboBox::from_id_salt("export.preset").selected_text(crate::i18n::t(&ex.preset)).width(ui.available_width() - 4.0).show_ui(
ui,
|ui| {
if !favs.is_empty() {
ui.label(egui::RichText::new(tl!("Favorites")).color(t.text_dim).size(11.0));
for f in &favs {
@@ -272,7 +273,8 @@ pub(super) fn settings_column(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: R
if ui.button(tl!("More presets…")).clicked() {
open_manager = true;
}
});
},
);
reg.add("export.preset", r.response.rect, ex.preset.clone());
});
ui.horizontal(|ui| {
+2 -2
View File
@@ -112,7 +112,7 @@ pub fn show(app: &mut FilmcraftApp, ctx: &egui::Context) {
elems.push(("exportFrame.name".into(), r.rect, d.name.clone()));
ui.end_row();
ui.label("Format:");
let combo = egui::ComboBox::from_id_salt("frame-export-format").width(292.0).selected_text(d.format.label()).show_ui(ui, |ui| {
let combo = craft_ui::ComboBox::from_id_salt("frame-export-format").width(292.0).selected_text(d.format.label()).show_ui(ui, |ui| {
for format in StillFormat::ALL {
let r = ui.selectable_value(&mut d.format, format, format.label());
elems.push((format!("exportFrame.format.{}", format.extension()), r.rect, format.label().into()));
@@ -128,7 +128,7 @@ pub fn show(app: &mut FilmcraftApp, ctx: &egui::Context) {
d.depth = 8;
}
ui.label("Depth:");
let combo = egui::ComboBox::from_id_salt("frame-export-depth").width(292.0).selected_text(format!("{} Bit", d.depth)).show_ui(ui, |ui| {
let combo = craft_ui::ComboBox::from_id_salt("frame-export-depth").width(292.0).selected_text(format!("{} Bit", d.depth)).show_ui(ui, |ui| {
for bits in [8, 16] {
let r = ui.add_enabled(bits == 8 || d.format.supports_16(), egui::Button::selectable(d.depth == bits, format!("{bits} Bit")));
if r.clicked() {
@@ -190,7 +190,7 @@ impl Ctx<'_> {
_ => 0,
};
let mut sel = cur;
let r = egui::ComboBox::from_id_salt(("gfxc", self.clip.0, self.layer, id))
let r = craft_ui::ComboBox::from_id_salt(("gfxc", self.clip.0, self.layer, id))
.selected_text(opts.get(cur).map_or("", |o| crate::i18n::t(o)))
.width(width)
.show_ui(vui, |ui| {
@@ -520,7 +520,7 @@ pub fn properties(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: Rect) {
};
let fams = filmcraft_text::families();
let (_, mut vui) = cx.row(ui, tl!("Font"));
let r = egui::ComboBox::from_id_salt(("gfx-font", clip.0, l)).selected_text(&family).width(170.0).height(400.0).show_ui(&mut vui, |ui| {
let r = craft_ui::ComboBox::from_id_salt(("gfx-font", clip.0, l)).selected_text(&family).width(170.0).height(400.0).show_ui(&mut vui, |ui| {
if !filmcraft_text::fonts::system_scanned() {
filmcraft_text::fonts::scan_system();
}
@@ -534,7 +534,7 @@ pub fn properties(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: Rect) {
cx.auto("font", response.rect, "Font");
let styles: Vec<String> = fams.iter().find(|(f, _)| f.eq_ignore_ascii_case(&family)).map(|(_, s)| s.clone()).unwrap_or_default();
let (_, mut vui) = cx.row(ui, tl!("Font Style"));
let r = egui::ComboBox::from_id_salt(("gfx-style", clip.0, l)).selected_text(&style).width(170.0).show_ui(&mut vui, |ui| {
let r = craft_ui::ComboBox::from_id_salt(("gfx-style", clip.0, l)).selected_text(&style).width(170.0).show_ui(&mut vui, |ui| {
for st in styles.iter().map(String::as_str).chain(["Bold", "Italic", "Bold Italic"]).filter({
let mut seen = Vec::new();
move |x: &&str| {
@@ -709,7 +709,7 @@ pub fn dialogs(app: &mut FilmcraftApp, ctx: &egui::Context) {
let id = egui::Id::unique("gfx-text-properties");
egui::Window::new(tl!("Text Properties")).id(id).collapsible(false).resizable(false).centered(ctx).show(ctx, |ui| {
ui.label(tl!("Text Layer Type"));
let r = egui::ComboBox::from_id_salt("gfx-text-type").selected_text(name(d.paragraph)).width(190.0).show_ui(ui, |ui| {
let r = craft_ui::ComboBox::from_id_salt("gfx-text-type").selected_text(name(d.paragraph)).width(190.0).show_ui(ui, |ui| {
for paragraph in [false, true] {
// vertical text has no box to wrap in
let r = ui.add_enabled_ui(!(paragraph && d.vertical), |ui| ui.selectable_label(d.paragraph == paragraph, name(paragraph))).inner;
@@ -183,7 +183,7 @@ fn browse(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: Rect) {
);
elems.push(("gfxTemplates.search".into(), r.rect, "Search".into()));
let cur = if st.category.is_empty() { tl!("All Categories").to_string() } else { crate::i18n::t(&st.category).to_string() };
let r = egui::ComboBox::from_id_salt("gfx-cat").selected_text(cur).width(130.0).show_ui(ui, |ui| {
let r = craft_ui::ComboBox::from_id_salt("gfx-cat").selected_text(cur).width(130.0).show_ui(ui, |ui| {
ui.selectable_value(&mut st.category, String::new(), tl!("All Categories"));
for c in &cats {
ui.selectable_value(&mut st.category, c.clone(), crate::i18n::t(c));
@@ -433,7 +433,7 @@ pub fn template_controls(
Some(ParamValue::Text(s)) => s,
_ => String::new(),
};
let r = egui::ComboBox::from_id_salt(("gfx-tpl-font", clip.0, c.id.as_str())).selected_text(&fam).width(170.0).height(360.0).show_ui(
let r = craft_ui::ComboBox::from_id_salt(("gfx-tpl-font", clip.0, c.id.as_str())).selected_text(&fam).width(170.0).height(360.0).show_ui(
&mut vui,
|ui| {
for (f, _) in filmcraft_text::families() {
@@ -478,7 +478,7 @@ pub fn responsive_time(app: &FilmcraftApp, ui: &mut egui::Ui, clip: ClipId, it:
}
let mut vui = row_label(ui, tl!("Roll"), &t);
let mut mode = m.roll.mode;
let r = egui::ComboBox::from_id_salt(("gfx-roll", clip.0)).selected_text(crate::i18n::t(mode.label())).width(120.0).show_ui(&mut vui, |ui| {
let r = craft_ui::ComboBox::from_id_salt(("gfx-roll", clip.0)).selected_text(crate::i18n::t(mode.label())).width(120.0).show_ui(&mut vui, |ui| {
for x in RollMode::ALL {
ui.selectable_value(&mut mode, x, crate::i18n::t(x.label()));
}
@@ -546,7 +546,7 @@ pub fn responsive_position(
.unwrap_or_default(),
};
let mut vui = row_label(ui, tl!("Pin To"), &t);
let r = egui::ComboBox::from_id_salt(("gfx-pin", clip.0, layer)).selected_text(&target_name).width(150.0).show_ui(&mut vui, |ui| {
let r = craft_ui::ComboBox::from_id_salt(("gfx-pin", clip.0, layer)).selected_text(&target_name).width(150.0).show_ui(&mut vui, |ui| {
if ui.selectable_label(pin.is_none(), tl!("None")).clicked() {
actions.push(("graphics.pin".into(), json!({"clip": clip.0, "layer": layer, "to": "none"})));
}
@@ -761,14 +761,14 @@ fn replace_dialog(app: &mut FilmcraftApp, ctx: &egui::Context) {
fams.dedup();
ui.horizontal(|ui| {
ui.label(tl!("Replace:"));
let r = egui::ComboBox::from_id_salt("gfx-rf-from").selected_text(&d.from).width(160.0).show_ui(ui, |ui| {
let r = craft_ui::ComboBox::from_id_salt("gfx-rf-from").selected_text(&d.from).width(160.0).show_ui(ui, |ui| {
for f in &fams {
ui.selectable_value(&mut d.from, f.clone(), f);
}
});
elems.push(("replaceFonts.from".into(), r.response.rect, "Replace".into()));
ui.label(tl!("with:"));
let r = egui::ComboBox::from_id_salt("gfx-rf-to").selected_text(&d.to).width(160.0).height(360.0).show_ui(ui, |ui| {
let r = craft_ui::ComboBox::from_id_salt("gfx-rf-to").selected_text(&d.to).width(160.0).height(360.0).show_ui(ui, |ui| {
for (f, _) in filmcraft_text::families() {
ui.selectable_value(&mut d.to, f.clone(), &f);
}
+8 -7
View File
@@ -106,7 +106,7 @@ pub fn show(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: Rect) {
ui.horizontal(|ui| {
ui.add_sized(vec2(110.0, 20.0), egui::Label::new(egui::RichText::new(tl!("Look")).color(t.text_dim)));
let mut sel = look;
egui::ComboBox::from_id_salt("lumetri-look")
craft_ui::ComboBox::from_id_salt("lumetri-look")
.selected_text(opts.get(look).map_or(tl!("None"), |o| crate::i18n::t(o)))
.width((ui.available_width() - 40.0).max(60.0))
.show_ui(ui, |ui| {
@@ -204,7 +204,7 @@ pub fn show(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: Rect) {
ui.add_sized(vec2(110.0, 20.0), egui::Label::new(egui::RichText::new(tl!("Show Mask")).color(t.text_dim)));
let mut sel = mask;
let modes = [tl!("Off"), tl!("Color/Gray"), tl!("Color/Black"), tl!("White/Black")];
egui::ComboBox::from_id_salt("hsl-mask").selected_text(modes[mask.min(3)]).show_ui(ui, |ui| {
craft_ui::ComboBox::from_id_salt("hsl-mask").selected_text(modes[mask.min(3)]).show_ui(ui, |ui| {
for (i, o) in modes.iter().enumerate() {
if ui.selectable_value(&mut sel, i, *o).changed() {
set(&mut actions, "hsl_show_mask", json!(i));
@@ -310,9 +310,11 @@ fn lut_combo(ui: &mut egui::Ui, app: &mut FilmcraftApp, e: &EffectInstance, para
let mut rect = Rect::NOTHING;
ui.horizontal(|ui| {
ui.add_sized(vec2(110.0, 20.0), egui::Label::new(egui::RichText::new(label).color(t.text_dim)));
let r = egui::ComboBox::from_id_salt(("lumetri-lut", param)).selected_text(shown).truncate().width((ui.available_width() - 40.0).max(60.0)).show_ui(
ui,
|ui| {
let r = craft_ui::ComboBox::from_id_salt(("lumetri-lut", param))
.selected_text(shown)
.truncate()
.width((ui.available_width() - 40.0).max(60.0))
.show_ui(ui, |ui| {
for (r, name) in &entries {
if ui.selectable_label(*r == cur, name).clicked() && *r != cur {
out.push((cmd, json!({"lut": r})));
@@ -322,8 +324,7 @@ fn lut_combo(ui: &mut egui::Ui, app: &mut FilmcraftApp, e: &EffectInstance, para
if ui.selectable_label(false, tl!("Browse…")).clicked() {
out.push((cmd, json!({"browse": true})));
}
},
);
});
rect = r.response.rect;
});
app.auto.add(&format!("lumetri.{param}"), rect, label);
+1 -1
View File
@@ -120,7 +120,7 @@ pub(crate) fn effect_rows(
let mut mode = m.mode;
crate::panels::effect_controls::fit_to_row(&mut mui);
let combo =
egui::ComboBox::from_id_salt(("mask-mode", clip.0, idx, k)).selected_text(crate::i18n::t(mode.label())).width(88.0).show_ui(&mut mui, |ui| {
craft_ui::ComboBox::from_id_salt(("mask-mode", clip.0, idx, k)).selected_text(crate::i18n::t(mode.label())).width(88.0).show_ui(&mut mui, |ui| {
for md in MaskMode::ALL {
if ui.selectable_value(&mut mode, md, crate::i18n::t(md.label())).changed() {
actions.push(("masks.set".into(), json!({"clip": clip.0, "effect": idx, "mask": k, "mode": md.label()})));
+1 -1
View File
@@ -422,7 +422,7 @@ fn combo(ui: &mut egui::Ui, elems: &mut Elems, id: &str, label: &str, value: &mu
}
let cur = value.as_str().unwrap_or_default().to_string();
let shown = options.iter().find(|o| o.0 == cur).map(|o| o.1.clone()).unwrap_or(cur.clone());
let r = egui::ComboBox::from_id_salt(id).selected_text(crate::i18n::t(&shown)).show_ui(ui, |ui| {
let r = craft_ui::ComboBox::from_id_salt(id).selected_text(crate::i18n::t(&shown)).show_ui(ui, |ui| {
for (k, l) in options {
if ui.selectable_label(cur == *k, crate::i18n::t(l)).clicked() {
*value = json!(k);
+1 -1
View File
@@ -78,7 +78,7 @@ pub fn show(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: Rect) {
if f.name == "Label" {
let cur = Label::from_name(&f.value).unwrap_or(Label::Violet);
let mut sel = cur;
let r = egui::ComboBox::from_id_salt(("md-label", item.0))
let r = craft_ui::ComboBox::from_id_salt(("md-label", item.0))
.selected_text(crate::i18n::t(cur.name()))
.width(ui.available_width().min(200.0))
.show_ui(ui, |ui| {
+1 -1
View File
@@ -129,7 +129,7 @@ pub fn show(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: Rect) {
let mut display = st.display;
let mut child = ui.new_child(egui::UiBuilder::new().max_rect(dr));
let label = |d: RefDisplay| if d == RefDisplay::Composite { tl!("Composite Video") } else { tl!("Lumetri Scopes") };
let cb = egui::ComboBox::from_id_salt("reference-display").selected_text(label(display)).width(134.0).show_ui(&mut child, |ui| {
let cb = craft_ui::ComboBox::from_id_salt("reference-display").selected_text(label(display)).width(134.0).show_ui(&mut child, |ui| {
for d in [RefDisplay::Composite, RefDisplay::Scopes] {
let r = ui.selectable_value(&mut display, d, label(d));
elems.push((format!("reference.display.{}", if d == RefDisplay::Composite { "composite" } else { "scopes" }), r.rect, label(d).into()));
+1 -1
View File
@@ -119,7 +119,7 @@ fn footer(app: &mut FilmcraftApp, ui: &mut egui::Ui, r: Rect, space: Option<Colo
let sr = Rect::from_min_size(pos2(r.max.x - scale_w - 6.0, r.min.y + 4.0), vec2(scale_w, 22.0));
let mut child = ui.new_child(egui::UiBuilder::new().max_rect(sr));
let mut scale = scale0;
let cb = egui::ComboBox::from_id_salt("scopes-scale").selected_text(crate::i18n::t(scale.label())).width(scale_w - 8.0).show_ui(&mut child, |ui| {
let cb = craft_ui::ComboBox::from_id_salt("scopes-scale").selected_text(crate::i18n::t(scale.label())).width(scale_w - 8.0).show_ui(&mut child, |ui| {
for s in Scale::ALL {
let r = ui.selectable_value(&mut scale, s, crate::i18n::t(s.label()));
app.auto.add(&format!("scopes.menu.scale.{}", s.label().replace(' ', "")), r.rect, s.label());
@@ -270,7 +270,9 @@ fn row<R>(ui: &mut egui::Ui, label_w: f32, label: &str, enabled: bool, add: impl
/// A greyed-out list showing `value`, with the "not supported yet" hint (or `hint`).
fn fixed_combo(ui: &mut egui::Ui, elems: &mut Elems, id: &str, value: &str, width: f32, hint: &str) {
let r = ui
.add_enabled_ui(false, |ui| egui::ComboBox::from_id_salt(("seq-settings-fixed", id)).selected_text(t(value)).width(width).show_ui(ui, |_| {}).response)
.add_enabled_ui(false, |ui| {
craft_ui::ComboBox::from_id_salt(("seq-settings-fixed", id)).selected_text(t(value)).width(width).show_ui(ui, |_| {}).response
})
.inner
.on_disabled_hover_text(t(hint));
elems.push((format!("sequenceSettings.{id}"), r.rect, value.to_string()));
@@ -313,7 +315,7 @@ fn general(ui: &mut egui::Ui, d: &mut SequenceSettingsDraft, cur: &SequenceSetti
// no row limit of our own: the list grows to egui's popup limit (`Spacing::default_area_size`,
// 400 px), so every rate up to 119.88 shows without scrolling (the default 200 px stopped
// before 29.97)
let resp = egui::ComboBox::from_id_salt("seq-settings-timebase").selected_text(&shown).width(list_w).height(f32::INFINITY).show_ui(ui, |ui| {
let resp = craft_ui::ComboBox::from_id_salt("seq-settings-timebase").selected_text(&shown).width(list_w).height(f32::INFINITY).show_ui(ui, |ui| {
for (num, den) in choices {
let label = timebase_label(FrameRate { num, den });
let o = ui.selectable_label((d.fps_num, d.fps_den) == (num, den), &label);
@@ -351,7 +353,7 @@ fn general(ui: &mut egui::Ui, d: &mut SequenceSettingsDraft, cur: &SequenceSetti
let formats = timecode_formats(r);
let chosen = if r.supports_drop_frame() { if d.drop_frame { "df" } else { "ndf" } } else { "tc" };
let shown = formats.iter().find(|(id, _)| *id == chosen).map(|(_, l)| l.clone()).unwrap_or_default();
let resp = egui::ComboBox::from_id_salt("seq-settings-video-display").selected_text(&shown).width(list_w).show_ui(ui, |ui| {
let resp = craft_ui::ComboBox::from_id_salt("seq-settings-video-display").selected_text(&shown).width(list_w).show_ui(ui, |ui| {
for (id, label) in &formats {
let o = ui.selectable_label(*id == chosen, label);
if o.clicked() {
@@ -369,7 +371,7 @@ fn general(ui: &mut egui::Ui, d: &mut SequenceSettingsDraft, cur: &SequenceSetti
section(ui, "Audio", |ui| {
row(ui, label_w, "Channel Format:", true, |ui| {
let shown = d.mix.clone();
let resp = egui::ComboBox::from_id_salt("seq-settings-mix").selected_text(t(&shown)).width(120.0).show_ui(ui, |ui| {
let resp = craft_ui::ComboBox::from_id_salt("seq-settings-mix").selected_text(t(&shown)).width(120.0).show_ui(ui, |ui| {
for m in MIXES {
let o = ui.selectable_value(&mut d.mix, m.to_string(), t(m));
elems.push((format!("sequenceSettings.channelFormat.option.{m}"), o.rect, m.to_string()));
@@ -391,7 +393,7 @@ fn general(ui: &mut egui::Ui, d: &mut SequenceSettingsDraft, cur: &SequenceSetti
rates.insert(0, d.sample_rate);
}
let shown = format!("{} Hz", d.sample_rate);
let resp = egui::ComboBox::from_id_salt("seq-settings-sample-rate").selected_text(&shown).width(list_w).show_ui(ui, |ui| {
let resp = craft_ui::ComboBox::from_id_salt("seq-settings-sample-rate").selected_text(&shown).width(list_w).show_ui(ui, |ui| {
for hz in rates {
let label = format!("{hz} Hz");
let o = ui.selectable_value(&mut d.sample_rate, hz, &label);
@@ -432,7 +434,7 @@ fn color(ui: &mut egui::Ui, d: &mut SequenceSettingsDraft, elems: &mut Elems) {
let label_w = ui.painter().layout_no_wrap(tl!("Working Color Space:").to_string(), font, egui::Color32::WHITE).size().x;
row(ui, label_w, "Working Color Space:", true, |ui| {
let shown = WorkingSpace::parse(&d.working_space).map_or(d.working_space.clone(), |w| t(w.label()).to_string());
let resp = egui::ComboBox::from_id_salt("seq-settings-working-space").selected_text(&shown).width(220.0).show_ui(ui, |ui| {
let resp = craft_ui::ComboBox::from_id_salt("seq-settings-working-space").selected_text(&shown).width(220.0).show_ui(ui, |ui| {
for w in WorkingSpace::ALL {
let o = ui.selectable_value(&mut d.working_space, w.id().to_string(), t(w.label()));
elems.push((format!("sequenceSettings.workingSpace.option.{}", w.id()), o.rect, w.label().to_string()));
+1 -1
View File
@@ -433,7 +433,7 @@ fn combo(app: &mut FilmcraftApp, ui: &mut Ui, key: &str, label: &str, current: &
let mut chosen = None;
let mut items: Vec<(String, Rect, String)> = Vec::new();
// the current choice is cut to the box's width (the open list shows it whole)
let r = egui::ComboBox::from_id_salt(("settings", key))
let r = craft_ui::ComboBox::from_id_salt(("settings", key))
.selected_text(RichText::new(crate::i18n::t(&shown)).size(12.5))
.width(width.min(ui.available_width()))
.truncate()
@@ -215,7 +215,7 @@ fn header(app: &mut FilmcraftApp, ui: &mut egui::Ui, t: &Tokens) {
ui.horizontal(|ui| {
ui.label(RichText::new(tl!("Preset:")).size(13.0).color(t.text_dim));
let shown = if modified { tlf!("{active} (modified)", active) } else { active.clone() };
let r = egui::ComboBox::from_id_salt("shortcuts-preset").width(280.0).selected_text(shown).show_ui(ui, |ui| {
let r = craft_ui::ComboBox::from_id_salt("shortcuts-preset").width(280.0).selected_text(shown).show_ui(ui, |ui| {
for name in builtin.iter().chain(custom.iter()) {
let r = ui.selectable_label(*name == active && !modified, name);
app.auto.add(&format!("shortcuts.preset.{name}"), r.rect, name);
@@ -283,7 +283,7 @@ fn header(app: &mut FilmcraftApp, ui: &mut egui::Ui, t: &Tokens) {
ui.horizontal(|ui| {
ui.label(RichText::new(tl!("Commands:")).size(13.0).color(t.text_dim));
let cur = app.shortcut_editor.context.clone();
let r = egui::ComboBox::from_id_salt("shortcuts-context")
let r = craft_ui::ComboBox::from_id_salt("shortcuts-context")
.width(280.0)
.selected_text(crate::i18n::t(if cur.is_empty() { APPLICATION } else { &cur }))
.show_ui(ui, |ui| {
+1 -1
View File
@@ -661,7 +661,7 @@ fn style_strip(app: &mut FilmcraftApp, ui: &mut egui::Ui, r: Rect, track_idx: us
let mut child = ui.new_child(egui::UiBuilder::new().max_rect(r).id_salt("caption-style"));
child.horizontal_centered(|ui| {
ui.spacing_mut().item_spacing.x = 6.0;
let r = egui::ComboBox::from_id_salt(("caption-font", tr.id.0)).selected_text(&st.font).width(130.0).height(400.0).show_ui(ui, |ui| {
let r = craft_ui::ComboBox::from_id_salt(("caption-font", tr.id.0)).selected_text(&st.font).width(130.0).height(400.0).show_ui(ui, |ui| {
if !filmcraft_text::fonts::system_scanned() {
filmcraft_text::fonts::scan_system();
}
@@ -137,7 +137,7 @@ pub fn effect_controls(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: Rect, id
let shown = align.label();
let r = ui
.add_enabled_ui(!one_sided, |ui| {
egui::ComboBox::from_id_salt(("tr-align", id.0))
craft_ui::ComboBox::from_id_salt(("tr-align", id.0))
.selected_text(shown)
.width(170.0)
.show_ui(ui, |ui| {
@@ -506,7 +506,7 @@ fn param_widget(ui: &mut egui::Ui, elems: &mut Elems, pid: &str, label: &str, ki
let shown = opts.get(*c as usize).copied().unwrap_or("");
let mut picked = None;
let mut options = Vec::new();
let r = egui::ComboBox::from_id_salt(("tr-param", pid.to_string())).selected_text(crate::i18n::t(shown)).show_ui(ui, |ui| {
let r = craft_ui::ComboBox::from_id_salt(("tr-param", pid.to_string())).selected_text(crate::i18n::t(shown)).show_ui(ui, |ui| {
for (i, o) in opts.iter().enumerate() {
let r = ui.selectable_label(i == *c as usize, crate::i18n::t(o));
if r.clicked() && i != *c as usize {
+3 -3
View File
@@ -244,7 +244,7 @@ pub fn show(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: Rect) {
ui.add_space(6.0);
label(ui, &t, "Language");
let lang_name = filmcraft_tts::LANGUAGES.iter().find(|(id, _)| *id == d.language).map_or(d.language.as_str(), |(_, n)| n);
let cb = egui::ComboBox::from_id_salt("tts-language").selected_text(lang_name).width(w).show_ui(ui, |ui| {
let cb = craft_ui::ComboBox::from_id_salt("tts-language").selected_text(lang_name).width(w).show_ui(ui, |ui| {
for (id, name) in filmcraft_tts::LANGUAGES {
if ui.selectable_label(d.language == *id, *name).clicked() {
d.language = id.to_string();
@@ -255,7 +255,7 @@ pub fn show(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: Rect) {
ui.add_space(10.0);
label(ui, &t, "Voice");
let voice_name = voices.iter().find(|v| v.id == d.voice).map_or(d.voice.as_str(), |v| v.name);
let cb = egui::ComboBox::from_id_salt("tts-voice").selected_text(voice_name).width(w).show_ui(ui, |ui| {
let cb = craft_ui::ComboBox::from_id_salt("tts-voice").selected_text(voice_name).width(w).show_ui(ui, |ui| {
for (engine, heading) in [("neural", "Natural voices"), ("basic", "Basic voices (built in)")] {
ui.label(egui::RichText::new(heading).color(t.text_faint).size(11.0));
for v in voices.iter().filter(|v| v.language == d.language && v.engine == engine) {
@@ -335,7 +335,7 @@ pub fn show(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: Rect) {
if open {
ui.add_space(4.0);
label(ui, &t, "Vocal pitch");
let cb = egui::ComboBox::from_id_salt("tts-pitch").selected_text(d.pitch.label()).width(w).show_ui(ui, |ui| {
let cb = craft_ui::ComboBox::from_id_salt("tts-pitch").selected_text(d.pitch.label()).width(w).show_ui(ui, |ui| {
for p in VocalPitch::ALL {
if ui.selectable_label(d.pitch == p, p.label()).clicked() {
d.pitch = p;
+2 -2
View File
@@ -249,7 +249,7 @@ fn settings_dialog(app: &mut FilmcraftApp, ctx: &egui::Context) {
ui.label(tl!("Source:"));
let shown = if d.prefs.source.is_empty() { tl!("Default Input").to_string() } else { d.prefs.source.clone() };
let previous_source = d.prefs.source.clone();
let cb = egui::ComboBox::from_id_salt("voiceover-source").selected_text(&shown).width(220.0).show_ui(ui, |ui| {
let cb = craft_ui::ComboBox::from_id_salt("voiceover-source").selected_text(&shown).width(220.0).show_ui(ui, |ui| {
let mut opts = vec![(String::new(), tl!("Default Input").to_string())];
opts.extend(d.devices.iter().map(|x| (x.clone(), x.clone())));
for (i, (val, label)) in opts.into_iter().enumerate() {
@@ -273,7 +273,7 @@ fn settings_dialog(app: &mut FilmcraftApp, ctx: &egui::Context) {
let n = u32::from(d.channels.clamp(1, 64));
d.prefs.input_channel = d.prefs.input_channel.min(63);
let cur = tlf!("Channel {n}", n = d.prefs.input_channel + 1);
let cb = egui::ComboBox::from_id_salt("voiceover-input").selected_text(&cur).width(220.0).show_ui(ui, |ui| {
let cb = craft_ui::ComboBox::from_id_salt("voiceover-input").selected_text(&cur).width(220.0).show_ui(ui, |ui| {
for c in 0..n.max(d.prefs.input_channel + 1) {
let label = tlf!("Channel {n}", n = c + 1);
let r = ui.selectable_label(d.prefs.input_channel == c, &label);