Compare commits

..
Author SHA1 Message Date
Dylan M. Taylor 3fb33855dc chore(clippy): enforce coding standards via workspace lints 2026-10-27 01:36:37 +00:00
15 changed files with 112 additions and 422 deletions
Generated
+18 -226
View File
@@ -753,15 +753,6 @@ 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"
@@ -943,40 +934,12 @@ dependencies = [
[[package]]
name = "craft-docpath"
version = "0.1.0"
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",
]
source = "git+https://github.com/storytold/craft-libs?rev=db19c373c8d59a545972ca340987aecfb3611a4b#db19c373c8d59a545972ca340987aecfb3611a4b"
[[package]]
name = "craft-media"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0"
source = "git+https://github.com/storytold/craft-libs?rev=db19c373c8d59a545972ca340987aecfb3611a4b#db19c373c8d59a545972ca340987aecfb3611a4b"
dependencies = [
"thiserror 2.0.21",
]
@@ -984,28 +947,20 @@ dependencies = [
[[package]]
name = "craft-ui"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0"
source = "git+https://github.com/storytold/craft-libs?rev=db19c373c8d59a545972ca340987aecfb3611a4b#db19c373c8d59a545972ca340987aecfb3611a4b"
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=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0"
source = "git+https://github.com/storytold/craft-libs?rev=db19c373c8d59a545972ca340987aecfb3611a4b#db19c373c8d59a545972ca340987aecfb3611a4b"
dependencies = [
"chrono",
"dunce",
@@ -1505,17 +1460,6 @@ 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"
@@ -1943,9 +1887,9 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
name = "icu_collections"
version = "2.3.0"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513"
checksum = "fe090a42a0d3140fed8c63263ed1fecc02422cb2f4bfe55cd8de2de1b8e105dd"
dependencies = [
"displaydoc",
"potential_utf",
@@ -1955,111 +1899,44 @@ 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"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb"
checksum = "25d29b57c17c6528d0dc4c2141cfa2d08200465acb537a3909c8b9b8a4a984c0"
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"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f"
checksum = "404a3b8c486f205c87fe7f23ab6894d84a4ad76d6e4671ae1981ba7def87c599"
dependencies = [
"icu_collections",
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec",
"utf16_iter",
"utf8_iter",
"write16",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
version = "2.3.0"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0"
[[package]]
name = "icu_pattern"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc43ce0e76ee8cb0482dc6e2dbb9b76278e8038f45daabf2a2877577e2ceb18f"
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",
]
checksum = "20f0088bfb7441f7a37fa0c9e20cb1e9cba1cac6b9ce8cd2a6dc44294f8e8cbc"
[[package]]
name = "icu_properties"
version = "2.3.0"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148"
checksum = "2ea83d991c56158f7cf898bf86436c1dfe7c07ee82fe88951f2695c9f37a379f"
dependencies = [
"displaydoc",
"icu_collections",
@@ -2072,21 +1949,18 @@ dependencies = [
[[package]]
name = "icu_properties_data"
version = "2.3.0"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa"
checksum = "b98f29af82d02b163fe50161388350e743bb3107cc8e8c2f7314cb505faddfcf"
[[package]]
name = "icu_provider"
version = "2.3.1"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73"
checksum = "413099315636c588ee8f011190b329b971a09a8fae9d157e01dcae8379468514"
dependencies = [
"displaydoc",
"icu_locale_core",
"postcard",
"serde",
"stable_deref_trait",
"writeable",
"yoke",
"zerofrom",
@@ -2094,30 +1968,6 @@ 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"
@@ -2409,9 +2259,6 @@ name = "litemap"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae"
dependencies = [
"serde_core",
]
[[package]]
name = "litrs"
@@ -3341,24 +3188,12 @@ 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",
]
@@ -4652,18 +4487,6 @@ 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 = "tar"
version = "0.4.46"
@@ -4787,7 +4610,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abc840c238154d58f9e86b2792261edfde565a17696e1cda1a449de27979801a"
dependencies = [
"displaydoc",
"serde_core",
"zerovec",
]
@@ -5025,12 +4847,6 @@ dependencies = [
"serde",
]
[[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"
@@ -5657,17 +5473,6 @@ 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",
"windows-strings",
]
[[package]]
name = "windows-result"
version = "0.4.1"
@@ -5895,20 +5700,11 @@ dependencies = [
"wayland-protocols-wlr",
]
[[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"
@@ -6163,11 +5959,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f"
dependencies = [
"displaydoc",
"litemap",
"serde_core",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
@@ -6176,7 +5969,6 @@ version = "0.11.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f58a2799cfe9a1670c7ea8edbd52ef8edb93cb37a7f83bf31ba9e35d9dae6a40"
dependencies = [
"serde",
"yoke",
"zerofrom",
"zerovec-derive",
+7 -5
View File
@@ -45,14 +45,14 @@ zip = { version = "9", default-features = false }
cpal = "0.18"
# Which file paths stored in a session may be opened.
craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0" }
craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "db19c373c8d59a545972ca340987aecfb3611a4b" }
# Video containers and bitstreams, shared with the other craft apps.
craft-media = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0" }
craft-update = { version = "=0.1.0", git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", features = ["egui"] }
craft-media = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "db19c373c8d59a545972ca340987aecfb3611a4b" }
craft-update = { version = "=0.1.0", git = "https://github.com/storytold/craft-libs", rev = "db19c373c8d59a545972ca340987aecfb3611a4b", features = ["egui"] }
# ui
craft-ui = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", features = ["serde"] }
craft-ui = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "db19c373c8d59a545972ca340987aecfb3611a4b", features = ["serde"] }
egui = { version = "0.37" }
eframe = { version = "0.37", default-features = false }
# Monotonic clock on native and the browser (the `web_time::Instant` panics-free equivalent).
@@ -65,9 +65,11 @@ image = { version = "0.25", default-features = false, features = ["png"] }
unsafe_code = "forbid"
[workspace.lints.clippy]
cognitive_complexity = "deny"
wildcard_imports = "deny"
dbg_macro = "warn"
todo = "warn"
too_many_arguments = "allow"
too_many_arguments = "deny"
type_complexity = "allow"
[profile.dev.package."*"]
+1 -4
View File
@@ -2,7 +2,4 @@ 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)" },
]
cognitive-complexity-threshold = 30
+3 -1
View File
@@ -20,7 +20,9 @@ log.workspace = true
unsafe_code = "deny"
[lints.clippy]
cognitive_complexity = "deny"
wildcard_imports = "deny"
dbg_macro = "warn"
todo = "warn"
too_many_arguments = "allow"
too_many_arguments = "deny"
type_complexity = "allow"
+3 -1
View File
@@ -32,7 +32,9 @@ libloading = "0.9"
unsafe_code = "deny"
[lints.clippy]
cognitive_complexity = "deny"
wildcard_imports = "deny"
dbg_macro = "warn"
todo = "warn"
too_many_arguments = "allow"
too_many_arguments = "deny"
type_complexity = "allow"
+1 -1
View File
@@ -221,7 +221,7 @@ fn contributors_toolbar(ui: &mut egui::Ui, v: &mut View) {
}
ui.separator();
let sort = ui.label("Sort");
let combo = craft_ui::ComboBox::from_id_salt("credits_sort").selected_text(v.key.label().0).show_ui(ui, |ui| {
let combo = egui::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;
+8 -8
View File
@@ -331,13 +331,13 @@ pub fn show(app: &mut SoundApp, ctx: &egui::Context) {
let create = ui.label("Create");
ui.add(egui::DragValue::new(count).range(1..=128)).labelled_by(create.id);
ui.label("new");
let fmt = craft_ui::ComboBox::from_id_salt("nt_fmt").selected_text(format.as_str()).show_ui(ui, |ui| {
let fmt = egui::ComboBox::from_id_salt("nt_fmt").selected_text(format.as_str()).show_ui(ui, |ui| {
for f in soundcraft_model::ChannelFormat::ALL {
ui.selectable_value(format, f.label().to_string(), f.label());
}
});
fmt.response.accessible_name("Channel format");
let kind_combo = craft_ui::ComboBox::from_id_salt("nt_kind")
let kind_combo = egui::ComboBox::from_id_salt("nt_kind")
.selected_text(soundcraft_model::TrackKind::from_id(kind).map_or("Audio Track", |k| k.label()))
.show_ui(ui, |ui| {
for k in soundcraft_model::TrackKind::ALL {
@@ -349,7 +349,7 @@ pub fn show(app: &mut SoundApp, ctx: &egui::Context) {
});
kind_combo.response.accessible_name("Track kind");
ui.label("in");
let tb = craft_ui::ComboBox::from_id_salt("nt_tb").selected_text(if timebase == "ticks" { "Ticks" } else { "Samples" }).show_ui(
let tb = egui::ComboBox::from_id_salt("nt_tb").selected_text(if timebase == "ticks" { "Ticks" } else { "Samples" }).show_ui(
ui,
|ui| {
ui.selectable_value(timebase, "samples".to_string(), "Samples");
@@ -372,7 +372,7 @@ pub fn show(app: &mut SoundApp, ctx: &egui::Context) {
ui.label("Name");
crate::widgets::named_text_field(ui.text_edit_singleline(name), "Name");
});
craft_ui::ComboBox::from_label("Sample Rate").selected_text(format!("{sample_rate} Hz")).show_ui(ui, |ui| {
egui::ComboBox::from_label("Sample Rate").selected_text(format!("{sample_rate} Hz")).show_ui(ui, |ui| {
for r in soundcraft_time::SampleRate::COMMON {
ui.selectable_value(sample_rate, r, format!("{r} Hz"));
}
@@ -403,12 +403,12 @@ pub fn show(app: &mut SoundApp, ctx: &egui::Context) {
}
});
ui.horizontal(|ui| {
craft_ui::ComboBox::from_label("Format").selected_text(format.to_uppercase()).show_ui(ui, |ui| {
egui::ComboBox::from_label("Format").selected_text(format.to_uppercase()).show_ui(ui, |ui| {
for f in ["wav", "aiff", "flac"] {
ui.selectable_value(format, f.to_string(), f.to_uppercase());
}
});
craft_ui::ComboBox::from_label("Bit Depth").selected_text(format!("{bit_depth}-bit")).show_ui(ui, |ui| {
egui::ComboBox::from_label("Bit Depth").selected_text(format!("{bit_depth}-bit")).show_ui(ui, |ui| {
for b in ["16", "24", "32f"] {
ui.selectable_value(bit_depth, b.to_string(), format!("{b}-bit"));
}
@@ -621,12 +621,12 @@ pub fn show(app: &mut SoundApp, ctx: &egui::Context) {
}
}
Dialog::Session { frame_rate, bit_depth } => {
craft_ui::ComboBox::from_label("Timecode Rate").selected_text(frame_rate.as_str()).show_ui(ui, |ui| {
egui::ComboBox::from_label("Timecode Rate").selected_text(frame_rate.as_str()).show_ui(ui, |ui| {
for r in soundcraft_time::FrameRate::ALL {
ui.selectable_value(frame_rate, r.label().to_string(), r.label());
}
});
craft_ui::ComboBox::from_label("Bit Depth").selected_text(bit_depth.as_str()).show_ui(ui, |ui| {
egui::ComboBox::from_label("Bit Depth").selected_text(bit_depth.as_str()).show_ui(ui, |ui| {
for b in ["16", "24", "32f"] {
ui.selectable_value(bit_depth, b.to_string(), b);
}
+5 -5
View File
@@ -153,7 +153,7 @@ fn tempo_ops(app: &mut SoundApp, ctx: &egui::Context) {
return;
}
egui::Window::new("Tempo Operations").open(&mut open).default_size(vec2(360.0, 200.0)).show(ctx, |ui| {
craft_ui::ComboBox::from_label("Curve").selected_text(app.ops.tempo_curve.clone()).show_ui(ui, |ui| {
egui::ComboBox::from_label("Curve").selected_text(app.ops.tempo_curve.clone()).show_ui(ui, |ui| {
for c in ["constant", "linear", "parabolic", "s-curve", "scale", "stretch"] {
ui.selectable_value(&mut app.ops.tempo_curve, c.to_string(), c);
}
@@ -183,7 +183,7 @@ fn time_ops(app: &mut SoundApp, ctx: &egui::Context) {
return;
}
egui::Window::new("Time Operations").open(&mut open).default_size(vec2(360.0, 200.0)).show(ctx, |ui| {
craft_ui::ComboBox::from_label("Operation").selected_text(app.ops.time_op.clone()).show_ui(ui, |ui| {
egui::ComboBox::from_label("Operation").selected_text(app.ops.time_op.clone()).show_ui(ui, |ui| {
for c in ["insert", "cut", "change meter", "move song start"] {
ui.selectable_value(&mut app.ops.time_op, c.to_string(), c);
}
@@ -193,7 +193,7 @@ fn time_ops(app: &mut SoundApp, ctx: &egui::Context) {
ui.horizontal(|ui| {
ui.add(egui::DragValue::new(&mut app.ops.meter_num).range(1..=64)).accessible_name("Meter numerator");
ui.label("/");
let den = craft_ui::ComboBox::from_id_salt("den").selected_text(app.ops.meter_den.to_string()).show_ui(ui, |ui| {
let den = egui::ComboBox::from_id_salt("den").selected_text(app.ops.meter_den.to_string()).show_ui(ui, |ui| {
for d in [2u32, 4, 8, 16] {
ui.selectable_value(&mut app.ops.meter_den, d, d.to_string());
}
@@ -228,14 +228,14 @@ fn midi_ops(app: &mut SoundApp, ctx: &egui::Context) {
return;
}
egui::Window::new("MIDI Operations").open(&mut open).default_size(vec2(360.0, 220.0)).show(ctx, |ui| {
craft_ui::ComboBox::from_label("Operation").selected_text(app.ops.midi_op.clone()).show_ui(ui, |ui| {
egui::ComboBox::from_label("Operation").selected_text(app.ops.midi_op.clone()).show_ui(ui, |ui| {
for c in ["quantize", "transpose", "change velocity", "flatten performance", "restore performance"] {
ui.selectable_value(&mut app.ops.midi_op, c.to_string(), c);
}
});
match app.ops.midi_op.as_str() {
"quantize" => {
craft_ui::ComboBox::from_label("Grid").selected_text(app.ops.quant_grid.clone()).show_ui(ui, |ui| {
egui::ComboBox::from_label("Grid").selected_text(app.ops.quant_grid.clone()).show_ui(ui, |ui| {
for g in ["1/4", "1/8", "1/8t", "1/16", "1/16t", "1/32"] {
ui.selectable_value(&mut app.ops.quant_grid, g.to_string(), g);
}
+1 -1
View File
@@ -155,7 +155,7 @@ fn original_time_stamp_row(ui: &mut Ui, s: &Session, d: &mut Spot, at: Samples)
pub fn fields(ui: &mut Ui, s: &Session, d: &mut Spot) {
egui::Grid::new("spot").num_columns(2).spacing(vec2(10.0, 6.0)).show(ui, |ui| {
let scale = ui.label("Time Scale");
let time_scale = craft_ui::ComboBox::from_id_salt("spot_scale").selected_text(d.format.label()).show_ui(ui, |ui| {
let time_scale = egui::ComboBox::from_id_salt("spot_scale").selected_text(d.format.label()).show_ui(ui, |ui| {
for f in TimeFormat::ALL {
ui.selectable_value(&mut d.format, f, f.label());
}
+11 -26
View File
@@ -197,24 +197,7 @@ impl MenuSearch {
/// `n` matches in a scrolling list, each drawn by `row` (told whether it is highlighted);
/// returns the one clicked, or the highlighted one on Enter.
pub(super) fn rows(&self, ui: &mut Ui, n: usize, max_height: f32, row: impl FnMut(&mut Ui, usize, bool) -> bool) -> Option<usize> {
let row_h = ui.spacing().interact_size.y;
egui::ScrollArea::vertical()
.id_salt(self.id.with("rows"))
.max_height(max_height)
.show_rows(ui, row_h, n, |ui, range| self.list_rows(ui, range, n, row))
.inner
}
/// The rows of `range` out of `n` matches, drawn into a list that scrolls and virtualises
/// itself (a shared dropdown menu's list); `rows` is the same list in a scroll area of its own.
pub(super) fn list_rows(
&self,
ui: &mut Ui,
range: std::ops::Range<usize>,
n: usize,
mut row: impl FnMut(&mut Ui, usize, bool) -> bool,
) -> Option<usize> {
pub(super) fn rows(&self, ui: &mut Ui, n: usize, max_height: f32, mut row: impl FnMut(&mut Ui, usize, bool) -> bool) -> Option<usize> {
let [up, down, enter] = self.keys;
let typing = self.typing();
let highlight = match (up, down) {
@@ -227,15 +210,17 @@ impl MenuSearch {
let mut picked = (typing && enter && n > 0).then_some(highlight);
let row_h = ui.spacing().interact_size.y;
let step = row_h + ui.spacing().item_spacing.y;
if typing && (up || down) {
let top = ui.max_rect().top() + (highlight as f32 - range.start as f32) * step;
ui.scroll_to_rect(Rect::from_min_size(pos2(ui.max_rect().left(), top), vec2(1.0, row_h)), None);
}
for i in range {
if row(ui, i, typing && i == highlight) {
picked = Some(i);
egui::ScrollArea::vertical().id_salt(self.id.with("rows")).max_height(max_height).show_rows(ui, row_h, n, |ui, range| {
if typing && (up || down) {
let top = ui.max_rect().top() + (highlight as f32 - range.start as f32) * step;
ui.scroll_to_rect(Rect::from_min_size(pos2(ui.max_rect().left(), top), vec2(1.0, row_h)), None);
}
}
for i in range {
if row(ui, i, typing && i == highlight) {
picked = Some(i);
}
}
});
picked
}
}
+45 -137
View File
@@ -2,7 +2,6 @@ use super::plugin_menu::{MenuSearch, menu_row};
use super::routing::route_name;
use super::slots::{COMPACT_SLOT, Mark, empty_slot, level_icon, used_slot};
use super::*;
use craft_ui::combo::Menu;
/// Draw one send slot: the route area picks a destination, while the level area opens or edits the send level.
pub(crate) fn send_slot(app: &mut SoundApp, ui: &mut Ui, track: &Track, slot: usize, r: Rect) {
@@ -54,8 +53,30 @@ pub(crate) fn send_slot(app: &mut SoundApp, ui: &mut Ui, track: &Track, slot: us
}
None => empty_slot(ui.painter(), r, ui.rect_contains_pointer(r), mark),
}
let current = snd.as_ref().map(|send| (send.target.clone(), route_name(app, &send.target)));
send_route_menu(app, ui, &route_resp, id, slot, current);
if let Some(send) = &snd {
egui::Popup::menu(&route_resp).close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside).show(|ui| {
ui.set_min_width(250.0);
ui.horizontal(|ui| {
ui.strong(format!("Send {}", (b'A' + slot as u8) as char));
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
ui.label(egui::RichText::new(route_name(app, &send.target)).color(Tokens::current().counter_text));
});
});
ui.label(egui::RichText::new("DESTINATION").size(8.0).color(Tokens::current().text_dim));
send_route_menu(app, ui, id, slot, Some(&send.target));
ui.separator();
if ui.button("Remove send").clicked() {
let _ = app.run("mix.send_remove", json!({"track": id.0, "slot": slot}));
ui.close();
}
});
} else {
egui::Popup::menu(&route_resp).close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside).show(|ui| {
ui.set_min_width(250.0);
ui.strong(format!("Send {} · Choose destination", (b'A' + slot as u8) as char));
send_route_menu(app, ui, id, slot, None);
});
}
}
/// The Send window, as in Pro Tools: one send's fader, level, pan, Mute and Pre-fader. Clicking a
@@ -320,143 +341,30 @@ fn send_level_scale(ui: &mut Ui, db: f32) {
ui.painter().line_segment([pos2(x, r.min.y), pos2(x, line_y + 3.0)], Stroke::new(2.0, t.counter_text));
}
/// What the send menu's footer was used for.
enum SendFooter {
NewBus,
RemoveSend,
}
/// The send's destination menu: a title and the bus search on top, the matching buses in a list
/// that scrolls, "new bus..." (and "Remove send" for an existing send) fixed under it.
/// `current` is the send's destination and its name, if the slot has a send.
fn send_route_menu(app: &mut SoundApp, ui: &Ui, field: &egui::Response, id: TrackId, slot: usize, current: Option<(Route, String)>) {
/// Searchable bus destination list with a New Bus action for a send.
fn send_route_menu(app: &mut SoundApp, ui: &mut Ui, id: TrackId, slot: usize, current: Option<&Route>) {
let busses = app.engine.session().busses.clone();
let which = (b'A' + slot as u8) as char;
let has_send = current.is_some();
let header = |ui: &mut Ui| {
ui.set_min_width(250.0);
match &current {
Some((_, name)) => {
ui.horizontal(|ui| {
ui.strong(format!("Send {which}"));
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
ui.label(egui::RichText::new(name).color(Tokens::current().counter_text));
});
});
ui.label(egui::RichText::new("DESTINATION").size(8.0).color(Tokens::current().text_dim));
}
None => {
ui.strong(format!("Send {which} · Choose destination"));
}
}
let search = MenuSearch::show(ui, egui::Id::unique(("send-bus-search", id.0, slot)), "Search buses…");
let visible: Vec<_> = busses.iter().filter(|bus| search.matches(&bus.name)).collect();
(search, visible)
};
let target = current.as_ref().map(|(route, _)| route);
let row_h = ui.spacing().interact_size.y;
let shown = Menu::new(ui, field).height(260.0).close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside).show_rows_with_header_footer(
header,
row_h,
|(_, visible)| visible.len(),
|ui, range, (search, visible)| {
let n = visible.len();
let picked = search.list_rows(ui, range, n, |ui, i, highlighted| {
let is_current = matches!(target, Some(Route::Bus(bus_id)) if *bus_id == visible[i].id);
menu_row(ui, highlighted || (is_current && !search.typing()), &visible[i].name)
});
picked.map(|i| {
ui.close();
visible[i].name.clone()
})
},
|ui| {
if ui.button("new bus...").clicked() {
ui.close();
return Some(SendFooter::NewBus);
}
if has_send && ui.button("Remove send").clicked() {
ui.close();
return Some(SendFooter::RemoveSend);
}
None
},
);
let Some((picked, footer)) = shown else { return };
if let Some(bus) = picked {
let _ = app.run("mix.send", json!({"track": id.0, "slot": slot, "bus": bus}));
let search = MenuSearch::show(ui, egui::Id::unique(("send-bus-search", id.0, slot)), "Search buses…");
let visible: Vec<_> = busses.iter().filter(|bus| search.matches(&bus.name)).collect();
let picked = search.rows(ui, visible.len(), 260.0, |ui, i, highlighted| {
let current = matches!(current, Some(Route::Bus(bus_id)) if *bus_id == visible[i].id);
menu_row(ui, highlighted || (current && !search.typing()), &visible[i].name)
});
if let Some(i) = picked {
let _ = app.run("mix.send", json!({"track": id.0, "slot": slot, "bus": visible[i].name}));
ui.close();
}
match footer {
Some(SendFooter::NewBus) => {
let mut n = busses.len() + 1;
while busses.iter().any(|bus| bus.name.eq_ignore_ascii_case(&format!("Bus {n}")))
|| app.engine.session().track_by_name(&format!("Bus {n}")).is_some()
{
n += 1;
}
if app.run("mix.new_bus", json!({"name": format!("Bus {n}")})).is_ok() {
let _ = app.run("mix.send", json!({"track": id.0, "slot": slot, "bus": format!("Bus {n}")}));
}
ui.separator();
if ui.button("new bus...").clicked() {
let mut n = busses.len() + 1;
while busses.iter().any(|bus| bus.name.eq_ignore_ascii_case(&format!("Bus {n}")))
|| app.engine.session().track_by_name(&format!("Bus {n}")).is_some()
{
n += 1;
}
Some(SendFooter::RemoveSend) => {
let _ = app.run("mix.send_remove", json!({"track": id.0, "slot": slot}));
if app.run("mix.new_bus", json!({"name": format!("Bus {n}")})).is_ok() {
let _ = app.run("mix.send", json!({"track": id.0, "slot": slot, "bus": format!("Bus {n}")}));
}
None => {}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Services;
use egui_kittest::{Harness, kittest::Queryable};
/// Filtering the destination list to nothing shrinks the menu; clearing the filter grows it
/// back, with the search field in the same place and the footer under the list.
#[test]
fn the_send_destination_menu_grows_back_when_the_search_is_cleared() {
let mut app = SoundApp::new(soundcraft_engine::demo::demo_engine(), None, Services::default());
for n in 0..30 {
app.run("mix.new_bus", json!({"name": format!("Aux {n}")})).unwrap();
}
let kick = app.engine.session().track_by_name("Kick").unwrap().id;
app.run("mix.send", json!({"track": kick.0, "slot": 0, "bus": "Aux 0"})).unwrap();
let _palette = crate::theme::lock_test_palette();
let mut ready = false;
let mut h = Harness::builder().with_size(vec2(600.0, 800.0)).build_ui_state(
move |ui, (app, kick): &mut (SoundApp, TrackId)| {
if !ready {
crate::fonts::install(ui.ctx());
crate::theme::apply(ui.ctx(), false);
ready = true;
return;
}
let track = app.engine.session().track(*kick).cloned().unwrap();
let r = Rect::from_min_size(ui.max_rect().min + vec2(20.0, 20.0), vec2(120.0, 24.0));
send_slot(app, ui, &track, 0, r);
},
(app, kick),
);
h.run();
h.get_by_label("Send A for Kick").click();
h.run();
let field = |h: &Harness<'_, (SoundApp, TrackId)>| h.get_by_role(egui::accesskit::Role::TextInput).rect();
let footer = |h: &Harness<'_, (SoundApp, TrackId)>| h.get_by_label("Remove send").rect();
let (search, full) = (field(&h), footer(&h));
assert!(h.query_by_label("new bus...").is_some(), "the footer lists new bus...");
assert!(full.top() > search.bottom() + 150.0, "the list sits between the search and the footer: {search:?} {full:?}");
h.get_by_role(egui::accesskit::Role::TextInput).type_text("zzzz");
h.run();
let none = footer(&h);
assert!(none.top() < full.top() - 100.0, "no matches shrink the menu: {none:?} vs {full:?}");
assert_eq!(field(&h), search, "the search field stays put");
for _ in 0..4 {
h.key_press(egui::Key::Backspace);
h.run();
}
assert_eq!(field(&h), search, "the search field stays put");
assert!((footer(&h).top() - full.top()).abs() < 1.0, "cleared, the footer is back under the full list: {:?} vs {full:?}", footer(&h));
ui.close();
}
}
+1 -1
View File
@@ -165,7 +165,7 @@ fn ui_customization(app: &mut SoundApp, ctx: &egui::Context) {
);
crate::widgets::checkbox(ui, &mut app.extra.reduce_motion, "Reduce motion");
let mut chosen = None;
craft_ui::ComboBox::from_label("Theme").selected_text(app.ui.theme.label()).show_ui(ui, |ui| {
egui::ComboBox::from_label("Theme").selected_text(app.ui.theme.label()).show_ui(ui, |ui| {
for mode in crate::theme::ThemeMode::ALL {
if ui.selectable_label(app.ui.theme == mode, mode.label()).clicked() {
chosen = Some(mode);
+1 -1
View File
@@ -70,7 +70,7 @@ pub(super) fn plugin_windows(app: &mut SoundApp, ctx: &egui::Context) {
let mut x = v;
let resp = if !p.choices.is_empty() {
let mut idx = x.round().max(0.0) as usize;
let r = craft_ui::ComboBox::from_id_salt(("pc", tid.0, slot, p.id))
let r = egui::ComboBox::from_id_salt(("pc", tid.0, slot, p.id))
.selected_text(p.choices.get(idx).copied().unwrap_or("?"))
.show_ui(ui, |ui| {
for (i, c) in p.choices.iter().enumerate() {
+4 -4
View File
@@ -452,7 +452,7 @@ fn chosen_device<'a>(devices: &'a [DeviceInfo], name: Option<&str>) -> Option<&'
fn device_combo(ui: &mut egui::Ui, id: &str, name: &str, value: &mut Option<String>, devices: &[DeviceInfo]) {
let default = devices.iter().find(|d| d.is_default).map_or("none".to_string(), |d| d.name.clone());
let shown = value.clone().unwrap_or_else(|| format!("Default ({default})"));
let combo = craft_ui::ComboBox::from_id_salt(id).width(300.0).selected_text(shown).show_ui(ui, |ui| {
let combo = egui::ComboBox::from_id_salt(id).width(300.0).selected_text(shown).show_ui(ui, |ui| {
ui.selectable_value(value, None, format!("Default ({default})"));
for d in devices {
ui.selectable_value(value, Some(d.name.clone()), format!("{} ({} ch)", d.name, d.max_channels));
@@ -464,7 +464,7 @@ fn device_combo(ui: &mut egui::Ui, id: &str, name: &str, value: &mut Option<Stri
/// Optional-number picker with a Device default entry and a unit, labelled for accessibility.
fn number_combo(ui: &mut egui::Ui, id: &str, name: &str, value: &mut Option<u32>, options: &[u32], unit: &str) {
let shown = value.map_or("Device default".to_string(), |v| format!("{v} {unit}"));
let combo = craft_ui::ComboBox::from_id_salt(id).width(300.0).selected_text(shown).show_ui(ui, |ui| {
let combo = egui::ComboBox::from_id_salt(id).width(300.0).selected_text(shown).show_ui(ui, |ui| {
ui.selectable_value(value, None, "Device default");
for v in options {
ui.selectable_value(value, Some(*v), format!("{v} {unit}"));
@@ -490,7 +490,7 @@ pub fn playback_engine(app: &mut SoundApp, ctx: &egui::Context) {
let system_label = ui.label("Audio system");
let def = systems.iter().find(|s| s.is_default).map_or("none".to_string(), |s| s.name.clone());
let shown = draft.system.clone().unwrap_or_else(|| format!("Default ({def})"));
let system = craft_ui::ComboBox::from_id_salt("pe-system").width(300.0).selected_text(shown).show_ui(ui, |ui| {
let system = egui::ComboBox::from_id_salt("pe-system").width(300.0).selected_text(shown).show_ui(ui, |ui| {
let before = draft.system.clone();
ui.selectable_value(&mut draft.system, None, format!("Default ({def})"));
for s in systems.iter() {
@@ -654,7 +654,7 @@ pub fn io_setup(app: &mut SoundApp, ctx: &egui::Context) {
let main = app.engine.session().main_format();
ui.horizontal(|ui| {
let main_label = ui.label("Main output");
let format = craft_ui::ComboBox::from_id_salt("main_output_format").selected_text(main.label()).show_ui(ui, |ui| {
let format = egui::ComboBox::from_id_salt("main_output_format").selected_text(main.label()).show_ui(ui, |ui| {
for f in soundcraft_model::ChannelFormat::ALL.into_iter().filter(|f| (2..=16).contains(&f.channels())) {
if ui.selectable_label(f == main, f.label()).clicked() && f != main {
let _ = app.run("setup.main_format", json!({"format": f.label()}));
+3 -1
View File
@@ -54,7 +54,9 @@ harness = false
unsafe_code = "deny"
[lints.clippy]
cognitive_complexity = "deny"
wildcard_imports = "deny"
dbg_macro = "warn"
todo = "warn"
too_many_arguments = "allow"
too_many_arguments = "deny"
type_complexity = "allow"