M0.12: xtask layers/wasm/ci gates (layer table, same-layer allow-list, UI crates barred below L5); cargo alias
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3f3265bb35
commit
581e31b432
7 files changed
+354
-59
No files matched your search
@@ -0,0 +1,2 @@
|
||||
[alias]
|
||||
xtask = "run --quiet --package xtask --"
|
||||
@@ -1773,7 +1773,9 @@ fn json_to_param(template: &ParamValue, v: &Value) -> Option<ParamValue> {
|
||||
ParamValue::Bool(_) => ParamValue::Bool(v.as_bool()?),
|
||||
ParamValue::Choice(_) => ParamValue::Choice(v.as_u64()? as u32),
|
||||
ParamValue::Text(_) => ParamValue::Text(v.as_str()?.to_string()),
|
||||
ParamValue::Curve(_) => ParamValue::Curve(v.as_array()?.iter().filter_map(|p| Some([p.get(0)?.as_f64()? as f32, p.get(1)?.as_f64()? as f32])).collect()),
|
||||
ParamValue::Curve(_) => {
|
||||
ParamValue::Curve(v.as_array()?.iter().filter_map(|p| Some([p.get(0)?.as_f64()? as f32, p.get(1)?.as_f64()? as f32])).collect())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,9 @@ pub enum ParamKind {
|
||||
Angle,
|
||||
Text,
|
||||
/// Tone curve (x in, y out); `hue` curves use a rainbow baseline with y = 0.5 as neutral.
|
||||
Curve { hue: bool },
|
||||
Curve {
|
||||
hue: bool,
|
||||
},
|
||||
/// Colour wheel offset (x, y in −1..1).
|
||||
Wheel,
|
||||
}
|
||||
@@ -402,42 +404,50 @@ fn build_effects() -> Vec<EffectDef> {
|
||||
grp(f("whites", "Whites", 0.0, -100.0, 100.0, ""), "Basic Correction"),
|
||||
grp(f("blacks", "Blacks", 0.0, -100.0, 100.0, ""), "Basic Correction"),
|
||||
grp(fs("saturation", "Saturation", 100.0, (0.0, 200.0), (0.0, 200.0), "", 1), "Basic Correction"),
|
||||
grp(ch("look", "Look", &["None", "Teal & Orange", "Warm Film", "Cool Blue", "Bleach Bypass", "Faded Matte", "Monochrome", "Golden Hour", "Night"], 0), "Creative"),
|
||||
grp(fs("look_intensity", "Intensity", 100.0, (0.0, 200.0), (0.0, 200.0), "", 1), "Creative"),
|
||||
grp(f("faded_film", "Faded Film", 0.0, 0.0, 100.0, ""), "Creative"),
|
||||
grp(
|
||||
ch(
|
||||
"look",
|
||||
"Look",
|
||||
&["None", "Teal & Orange", "Warm Film", "Cool Blue", "Bleach Bypass", "Faded Matte", "Monochrome", "Golden Hour", "Night"],
|
||||
0,
|
||||
),
|
||||
"Creative",
|
||||
),
|
||||
grp(fs("look_intensity", "Intensity", 100.0, (0.0, 200.0), (0.0, 200.0), "", 1), "Creative"),
|
||||
grp(f("faded_film", "Faded Film", 0.0, 0.0, 100.0, ""), "Creative"),
|
||||
grp(f("sharpen", "Sharpen", 0.0, -100.0, 100.0, ""), "Creative"),
|
||||
grp(f("vibrance", "Vibrance", 0.0, -100.0, 100.0, ""), "Creative"),
|
||||
grp(fs("creative_sat", "Saturation", 100.0, (0.0, 200.0), (0.0, 200.0), "", 1), "Creative"),
|
||||
grp(col("shadow_tint", "Shadow Tint", [0.5, 0.5, 0.5, 1.0]), "Creative"),
|
||||
grp(col("highlight_tint", "Highlight Tint", [0.5, 0.5, 0.5, 1.0]), "Creative"),
|
||||
grp(curve("curve_luma", "Luma Curve", false), "Curves"),
|
||||
grp(curve("curve_red", "Red Curve", false), "Curves"),
|
||||
grp(curve("curve_green", "Green Curve", false), "Curves"),
|
||||
grp(curve("curve_blue", "Blue Curve", false), "Curves"),
|
||||
grp(curve("hue_vs_sat", "Hue vs Sat", true), "Curves"),
|
||||
grp(curve("hue_vs_hue", "Hue vs Hue", true), "Curves"),
|
||||
grp(curve("hue_vs_luma", "Hue vs Luma", true), "Curves"),
|
||||
grp(curve("luma_vs_sat", "Luma vs Sat", true), "Curves"),
|
||||
grp(curve("sat_vs_sat", "Sat vs Sat", true), "Curves"),
|
||||
grp(wheel("wheel_shadows", "Shadows"), "Color Wheels & Match"),
|
||||
grp(f("wheel_shadows_l", "Shadows Lightness", 0.0, -100.0, 100.0, ""), "Color Wheels & Match"),
|
||||
grp(wheel("wheel_midtones", "Midtones"), "Color Wheels & Match"),
|
||||
grp(f("wheel_midtones_l", "Midtones Lightness", 0.0, -100.0, 100.0, ""), "Color Wheels & Match"),
|
||||
grp(wheel("wheel_highlights", "Highlights"), "Color Wheels & Match"),
|
||||
grp(f("wheel_highlights_l", "Highlights Lightness", 0.0, -100.0, 100.0, ""), "Color Wheels & Match"),
|
||||
grp(b("hsl_on", "Enable HSL Secondary", false), "HSL Secondary"),
|
||||
grp(fs("hsl_hue", "Hue Center", 0.0, (0.0, 360.0), (0.0, 360.0), "°", 0), "HSL Secondary"),
|
||||
grp(fs("hsl_hue_range", "Hue Range", 30.0, (1.0, 180.0), (1.0, 180.0), "°", 0), "HSL Secondary"),
|
||||
grp(fs("hsl_sat_min", "Saturation Min", 10.0, (0.0, 100.0), (0.0, 100.0), "", 0), "HSL Secondary"),
|
||||
grp(fs("hsl_luma_min", "Luma Min", 5.0, (0.0, 100.0), (0.0, 100.0), "", 0), "HSL Secondary"),
|
||||
grp(fs("hsl_luma_max", "Luma Max", 95.0, (0.0, 100.0), (0.0, 100.0), "", 0), "HSL Secondary"),
|
||||
grp(f("hsl_soft", "Soften", 20.0, 0.0, 100.0, ""), "HSL Secondary"),
|
||||
grp(ch("hsl_show_mask", "Show Mask", &["Off", "Color/Gray", "Color/Black", "White/Black"], 0), "HSL Secondary"),
|
||||
grp(f("hsl_temp", "Temperature", 0.0, -100.0, 100.0, ""), "HSL Secondary"),
|
||||
grp(f("hsl_tint", "Tint", 0.0, -100.0, 100.0, ""), "HSL Secondary"),
|
||||
grp(fs("hsl_sat", "Saturation", 100.0, (0.0, 200.0), (0.0, 200.0), "", 1), "HSL Secondary"),
|
||||
grp(f("hsl_hue_shift", "Hue Shift", 0.0, -180.0, 180.0, "°"), "HSL Secondary"),
|
||||
grp(f("vignette_amount", "Amount", 0.0, -5.0, 5.0, ""), "Vignette"),
|
||||
grp(curve("curve_red", "Red Curve", false), "Curves"),
|
||||
grp(curve("curve_green", "Green Curve", false), "Curves"),
|
||||
grp(curve("curve_blue", "Blue Curve", false), "Curves"),
|
||||
grp(curve("hue_vs_sat", "Hue vs Sat", true), "Curves"),
|
||||
grp(curve("hue_vs_hue", "Hue vs Hue", true), "Curves"),
|
||||
grp(curve("hue_vs_luma", "Hue vs Luma", true), "Curves"),
|
||||
grp(curve("luma_vs_sat", "Luma vs Sat", true), "Curves"),
|
||||
grp(curve("sat_vs_sat", "Sat vs Sat", true), "Curves"),
|
||||
grp(wheel("wheel_shadows", "Shadows"), "Color Wheels & Match"),
|
||||
grp(f("wheel_shadows_l", "Shadows Lightness", 0.0, -100.0, 100.0, ""), "Color Wheels & Match"),
|
||||
grp(wheel("wheel_midtones", "Midtones"), "Color Wheels & Match"),
|
||||
grp(f("wheel_midtones_l", "Midtones Lightness", 0.0, -100.0, 100.0, ""), "Color Wheels & Match"),
|
||||
grp(wheel("wheel_highlights", "Highlights"), "Color Wheels & Match"),
|
||||
grp(f("wheel_highlights_l", "Highlights Lightness", 0.0, -100.0, 100.0, ""), "Color Wheels & Match"),
|
||||
grp(b("hsl_on", "Enable HSL Secondary", false), "HSL Secondary"),
|
||||
grp(fs("hsl_hue", "Hue Center", 0.0, (0.0, 360.0), (0.0, 360.0), "°", 0), "HSL Secondary"),
|
||||
grp(fs("hsl_hue_range", "Hue Range", 30.0, (1.0, 180.0), (1.0, 180.0), "°", 0), "HSL Secondary"),
|
||||
grp(fs("hsl_sat_min", "Saturation Min", 10.0, (0.0, 100.0), (0.0, 100.0), "", 0), "HSL Secondary"),
|
||||
grp(fs("hsl_luma_min", "Luma Min", 5.0, (0.0, 100.0), (0.0, 100.0), "", 0), "HSL Secondary"),
|
||||
grp(fs("hsl_luma_max", "Luma Max", 95.0, (0.0, 100.0), (0.0, 100.0), "", 0), "HSL Secondary"),
|
||||
grp(f("hsl_soft", "Soften", 20.0, 0.0, 100.0, ""), "HSL Secondary"),
|
||||
grp(ch("hsl_show_mask", "Show Mask", &["Off", "Color/Gray", "Color/Black", "White/Black"], 0), "HSL Secondary"),
|
||||
grp(f("hsl_temp", "Temperature", 0.0, -100.0, 100.0, ""), "HSL Secondary"),
|
||||
grp(f("hsl_tint", "Tint", 0.0, -100.0, 100.0, ""), "HSL Secondary"),
|
||||
grp(fs("hsl_sat", "Saturation", 100.0, (0.0, 200.0), (0.0, 200.0), "", 1), "HSL Secondary"),
|
||||
grp(f("hsl_hue_shift", "Hue Shift", 0.0, -180.0, 180.0, "°"), "HSL Secondary"),
|
||||
grp(f("vignette_amount", "Amount", 0.0, -5.0, 5.0, ""), "Vignette"),
|
||||
grp(f("vignette_midpoint", "Midpoint", 50.0, 0.0, 100.0, ""), "Vignette"),
|
||||
grp(f("vignette_roundness", "Roundness", 0.0, -100.0, 100.0, ""), "Vignette"),
|
||||
grp(f("vignette_feather", "Feather", 50.0, 0.0, 100.0, ""), "Vignette"),
|
||||
|
||||
@@ -1295,7 +1295,18 @@ fn lumetri_advanced(img: &mut Image, e: &EffectInstance, cx: &FxCtx) {
|
||||
let (ls, lm, lh) = (f(e, "wheel_shadows_l", cx) / 100.0, f(e, "wheel_midtones_l", cx) / 100.0, f(e, "wheel_highlights_l", cx) / 100.0);
|
||||
let wheels = ws.iter().chain(&wm).chain(&wh).any(|v| v.abs() > 1e-5) || ls.abs() + lm.abs() + lh.abs() > 1e-5;
|
||||
let hsl_on = b(e, "hsl_on");
|
||||
let any = luma_c.is_some() || red_c.is_some() || green_c.is_some() || blue_c.is_some() || hvs.is_some() || hvh.is_some() || hvl.is_some() || lvs.is_some() || svs.is_some() || look > 0 || wheels || hsl_on;
|
||||
let any = luma_c.is_some()
|
||||
|| red_c.is_some()
|
||||
|| green_c.is_some()
|
||||
|| blue_c.is_some()
|
||||
|| hvs.is_some()
|
||||
|| hvh.is_some()
|
||||
|| hvl.is_some()
|
||||
|| lvs.is_some()
|
||||
|| svs.is_some()
|
||||
|| look > 0
|
||||
|| wheels
|
||||
|| hsl_on;
|
||||
if !any {
|
||||
return;
|
||||
}
|
||||
@@ -1303,7 +1314,8 @@ fn lumetri_advanced(img: &mut Image, e: &EffectInstance, cx: &FxCtx) {
|
||||
let (smin, lmin, lmax) = (f(e, "hsl_sat_min", cx) / 100.0, f(e, "hsl_luma_min", cx) / 100.0, f(e, "hsl_luma_max", cx) / 100.0);
|
||||
let soft = (f(e, "hsl_soft", cx) / 100.0 * 0.3).max(0.01);
|
||||
let show_mask = choice(e, "hsl_show_mask");
|
||||
let (htemp, htint, hsat, hshift) = (f(e, "hsl_temp", cx) / 100.0, f(e, "hsl_tint", cx) / 100.0, f(e, "hsl_sat", cx) / 100.0, f(e, "hsl_hue_shift", cx) / 360.0);
|
||||
let (htemp, htint, hsat, hshift) =
|
||||
(f(e, "hsl_temp", cx) / 100.0, f(e, "hsl_tint", cx) / 100.0, f(e, "hsl_sat", cx) / 100.0, f(e, "hsl_hue_shift", cx) / 360.0);
|
||||
let sample = |l: &Vec<f32>, x: f32| {
|
||||
let p = x.clamp(0.0, 1.0) * (N - 1) as f32;
|
||||
let i = p as usize;
|
||||
|
||||
@@ -58,7 +58,14 @@ pub fn show(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
slider(ui, &e, mt, "tint", "Tint", Some(Gradient::Tint), &t, &mut actions, clip, idx);
|
||||
slider(ui, &e, mt, "saturation", "Saturation", None, &t, &mut actions, clip, idx);
|
||||
sub(ui, &t, "Light");
|
||||
for (id, label) in [("exposure", "Exposure"), ("contrast", "Contrast"), ("highlights", "Highlights"), ("shadows", "Shadows"), ("whites", "Whites"), ("blacks", "Blacks")] {
|
||||
for (id, label) in [
|
||||
("exposure", "Exposure"),
|
||||
("contrast", "Contrast"),
|
||||
("highlights", "Highlights"),
|
||||
("shadows", "Shadows"),
|
||||
("whites", "Whites"),
|
||||
("blacks", "Blacks"),
|
||||
] {
|
||||
slider(ui, &e, mt, id, label, None, &t, &mut actions, clip, idx);
|
||||
}
|
||||
}
|
||||
@@ -67,17 +74,23 @@ pub fn show(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
Some(ParamValue::Choice(c)) => *c as usize,
|
||||
_ => 0,
|
||||
};
|
||||
let opts = filmcraft_project::find_effect("lumetri").and_then(|d| d.param("look")).and_then(|p| if let filmcraft_project::ParamKind::Choice(o) = p.kind { Some(o) } else { None }).unwrap_or(&[]);
|
||||
let opts = filmcraft_project::find_effect("lumetri")
|
||||
.and_then(|d| d.param("look"))
|
||||
.and_then(|p| if let filmcraft_project::ParamKind::Choice(o) = p.kind { Some(o) } else { None })
|
||||
.unwrap_or(&[]);
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_sized(vec2(110.0, 20.0), egui::Label::new(egui::RichText::new("Look").color(t.text_dim)));
|
||||
let mut sel = look;
|
||||
egui::ComboBox::from_id_salt("lumetri-look").selected_text(opts.get(look).copied().unwrap_or("None")).width(ui.available_width() - 8.0).show_ui(ui, |ui| {
|
||||
for (i, o) in opts.iter().enumerate() {
|
||||
if ui.selectable_value(&mut sel, i, *o).changed() {
|
||||
set(&mut actions, "look", json!(i));
|
||||
egui::ComboBox::from_id_salt("lumetri-look")
|
||||
.selected_text(opts.get(look).copied().unwrap_or("None"))
|
||||
.width(ui.available_width() - 8.0)
|
||||
.show_ui(ui, |ui| {
|
||||
for (i, o) in opts.iter().enumerate() {
|
||||
if ui.selectable_value(&mut sel, i, *o).changed() {
|
||||
set(&mut actions, "look", json!(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
slider(ui, &e, mt, "look_intensity", "Intensity", None, &t, &mut actions, clip, idx);
|
||||
sub(ui, &t, "Adjustments");
|
||||
@@ -90,7 +103,9 @@ pub fn show(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
let key = egui::Id::new("lumetri-curve-channel");
|
||||
let mut ch: usize = ui.data(|d| d.get_temp(key)).unwrap_or(0);
|
||||
ui.horizontal(|ui| {
|
||||
for (i, c) in [Color32::WHITE, Color32::from_rgb(230, 70, 70), Color32::from_rgb(80, 200, 90), Color32::from_rgb(80, 130, 250)].iter().enumerate() {
|
||||
for (i, c) in
|
||||
[Color32::WHITE, Color32::from_rgb(230, 70, 70), Color32::from_rgb(80, 200, 90), Color32::from_rgb(80, 130, 250)].iter().enumerate()
|
||||
{
|
||||
let (r, resp) = ui.allocate_exact_size(vec2(18.0, 18.0), Sense::click());
|
||||
ui.painter().circle_filled(r.center(), 6.0, *c);
|
||||
if ch == i {
|
||||
@@ -102,10 +117,21 @@ pub fn show(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
}
|
||||
});
|
||||
ui.data_mut(|d| d.insert_temp(key, ch));
|
||||
let (id, col) = [("curve_luma", Color32::WHITE), ("curve_red", Color32::from_rgb(230, 70, 70)), ("curve_green", Color32::from_rgb(80, 200, 90)), ("curve_blue", Color32::from_rgb(80, 130, 250))][ch];
|
||||
let (id, col) = [
|
||||
("curve_luma", Color32::WHITE),
|
||||
("curve_red", Color32::from_rgb(230, 70, 70)),
|
||||
("curve_green", Color32::from_rgb(80, 200, 90)),
|
||||
("curve_blue", Color32::from_rgb(80, 130, 250)),
|
||||
][ch];
|
||||
curve_editor(ui, &e, id, false, col, &t, &mut actions, clip, idx);
|
||||
sub(ui, &t, "Hue Saturation Curves");
|
||||
for (id, label) in [("hue_vs_sat", "Hue vs Sat"), ("hue_vs_hue", "Hue vs Hue"), ("hue_vs_luma", "Hue vs Luma"), ("luma_vs_sat", "Luma vs Sat"), ("sat_vs_sat", "Sat vs Sat")] {
|
||||
for (id, label) in [
|
||||
("hue_vs_sat", "Hue vs Sat"),
|
||||
("hue_vs_hue", "Hue vs Hue"),
|
||||
("hue_vs_luma", "Hue vs Luma"),
|
||||
("luma_vs_sat", "Luma vs Sat"),
|
||||
("sat_vs_sat", "Sat vs Sat"),
|
||||
] {
|
||||
ui.label(egui::RichText::new(label).color(t.text_dim));
|
||||
curve_editor(ui, &e, id, true, Color32::WHITE, &t, &mut actions, clip, idx);
|
||||
}
|
||||
@@ -113,7 +139,11 @@ pub fn show(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
if section(ui, app, "Color Wheels & Match") {
|
||||
let w = ((ui.available_width() - 24.0) / 3.0).min(120.0);
|
||||
ui.horizontal(|ui| {
|
||||
for (id, lid, label) in [("wheel_shadows", "wheel_shadows_l", "Shadows"), ("wheel_midtones", "wheel_midtones_l", "Midtones"), ("wheel_highlights", "wheel_highlights_l", "Highlights")] {
|
||||
for (id, lid, label) in [
|
||||
("wheel_shadows", "wheel_shadows_l", "Shadows"),
|
||||
("wheel_midtones", "wheel_midtones_l", "Midtones"),
|
||||
("wheel_highlights", "wheel_highlights_l", "Highlights"),
|
||||
] {
|
||||
ui.vertical(|ui| {
|
||||
ui.label(egui::RichText::new(label).color(t.text_dim));
|
||||
color_wheel(ui, &e, mt, id, lid, w, &t, &mut actions, clip, idx);
|
||||
@@ -129,7 +159,13 @@ pub fn show(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
}
|
||||
sub(ui, &t, "Key");
|
||||
slider(ui, &e, mt, "hsl_hue", "Hue", Some(Gradient::Hue), &t, &mut actions, clip, idx);
|
||||
for (id, label) in [("hsl_hue_range", "Hue Range"), ("hsl_sat_min", "Saturation Min"), ("hsl_luma_min", "Luma Min"), ("hsl_luma_max", "Luma Max"), ("hsl_soft", "Soften")] {
|
||||
for (id, label) in [
|
||||
("hsl_hue_range", "Hue Range"),
|
||||
("hsl_sat_min", "Saturation Min"),
|
||||
("hsl_luma_min", "Luma Min"),
|
||||
("hsl_luma_max", "Luma Max"),
|
||||
("hsl_soft", "Soften"),
|
||||
] {
|
||||
slider(ui, &e, mt, id, label, None, &t, &mut actions, clip, idx);
|
||||
}
|
||||
let mask = match e.param("hsl_show_mask").map(|p| &p.value) {
|
||||
@@ -154,7 +190,9 @@ pub fn show(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
slider(ui, &e, mt, "hsl_hue_shift", "Hue Shift", Some(Gradient::Hue), &t, &mut actions, clip, idx);
|
||||
}
|
||||
if section(ui, app, "Vignette") {
|
||||
for (id, label) in [("vignette_amount", "Amount"), ("vignette_midpoint", "Midpoint"), ("vignette_roundness", "Roundness"), ("vignette_feather", "Feather")] {
|
||||
for (id, label) in
|
||||
[("vignette_amount", "Amount"), ("vignette_midpoint", "Midpoint"), ("vignette_roundness", "Roundness"), ("vignette_feather", "Feather")]
|
||||
{
|
||||
slider(ui, &e, mt, id, label, None, &t, &mut actions, clip, idx);
|
||||
}
|
||||
}
|
||||
@@ -174,7 +212,12 @@ fn section(ui: &mut egui::Ui, app: &mut FilmcraftApp, name: &str) -> bool {
|
||||
ui.add_space(2.0);
|
||||
let (r, resp) = ui.allocate_exact_size(vec2(ui.available_width(), 40.0), Sense::click());
|
||||
ui.painter().line_segment([r.left_top(), r.right_top()], Stroke::new(1.0, t.separator));
|
||||
icons::paint(ui.painter(), Rect::from_center_size(pos2(r.min.x + 6.0, r.center().y), vec2(10.0, 10.0)), if open { Icon::ChevronDown } else { Icon::ChevronRight }, t.text_dim);
|
||||
icons::paint(
|
||||
ui.painter(),
|
||||
Rect::from_center_size(pos2(r.min.x + 6.0, r.center().y), vec2(10.0, 10.0)),
|
||||
if open { Icon::ChevronDown } else { Icon::ChevronRight },
|
||||
t.text_dim,
|
||||
);
|
||||
ui.painter().text(pos2(r.min.x + 20.0, r.center().y), Align2::LEFT_CENTER, name, Tokens::semibold(13.0), t.text);
|
||||
// toggle switch (visual; section bypass lands with per-section enables)
|
||||
let sw = Rect::from_center_size(pos2(r.max.x - 18.0, r.center().y), vec2(26.0, 14.0));
|
||||
@@ -207,7 +250,18 @@ enum Gradient {
|
||||
|
||||
/// Spectrum slider: 1 pt track (or gradient), hollow ring knob, scrubby value at the right.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn slider(ui: &mut egui::Ui, e: &EffectInstance, mt: Tick, id: &str, label: &str, grad: Option<Gradient>, t: &Tokens, actions: &mut Actions, clip: ClipId, idx: usize) {
|
||||
fn slider(
|
||||
ui: &mut egui::Ui,
|
||||
e: &EffectInstance,
|
||||
mt: Tick,
|
||||
id: &str,
|
||||
label: &str,
|
||||
grad: Option<Gradient>,
|
||||
t: &Tokens,
|
||||
actions: &mut Actions,
|
||||
clip: ClipId,
|
||||
idx: usize,
|
||||
) {
|
||||
let Some(pd) = e.def().and_then(|d| d.param(id)) else { return };
|
||||
let filmcraft_project::ParamKind::Float { min, max, soft_min, soft_max, decimals, .. } = pd.kind else { return };
|
||||
let v = e.param(id).map(|p| p.f64_at(mt)).unwrap_or(pd.default.as_f64().unwrap_or(0.0));
|
||||
@@ -240,7 +294,11 @@ fn slider(ui: &mut egui::Ui, e: &EffectInstance, mt: Tick, id: &str, label: &str
|
||||
let hit = Rect::from_min_max(pos2(track.min.x - 6.0, row.min.y), pos2(track.max.x + 6.0, row.max.y));
|
||||
let resp = ui.interact(hit, egui::Id::new(("lslider", clip.0, id)), Sense::click_and_drag());
|
||||
ui.painter().circle_filled(pos2(kx, track.center().y), 5.0, t.panel_bg);
|
||||
ui.painter().circle_stroke(pos2(kx, track.center().y), 5.0, Stroke::new(1.5, if resp.dragged() { t.hot_text } else { Color32::from_rgb(0xb0, 0xb0, 0xb0) }));
|
||||
ui.painter().circle_stroke(
|
||||
pos2(kx, track.center().y),
|
||||
5.0,
|
||||
Stroke::new(1.5, if resp.dragged() { t.hot_text } else { Color32::from_rgb(0xb0, 0xb0, 0xb0) }),
|
||||
);
|
||||
let mut out = None;
|
||||
if (resp.dragged() || resp.clicked())
|
||||
&& let Some(p) = resp.interact_pointer_pos()
|
||||
@@ -251,8 +309,13 @@ fn slider(ui: &mut egui::Ui, e: &EffectInstance, mt: Tick, id: &str, label: &str
|
||||
if resp.double_clicked() {
|
||||
out = pd.default.as_f64();
|
||||
}
|
||||
let mut vui = ui.new_child(egui::UiBuilder::new().max_rect(Rect::from_min_max(pos2(row.max.x - 52.0, row.min.y + 3.0), pos2(row.max.x, row.max.y - 3.0))).layout(egui::Layout::right_to_left(egui::Align::Center)));
|
||||
let (_, nv) = crate::widgets::hot_number(&mut vui, egui::Id::new(("lval", clip.0, id)), v, (soft_max - soft_min) / 300.0, (min, max), decimals as usize, "", t);
|
||||
let mut vui = ui.new_child(
|
||||
egui::UiBuilder::new()
|
||||
.max_rect(Rect::from_min_max(pos2(row.max.x - 52.0, row.min.y + 3.0), pos2(row.max.x, row.max.y - 3.0)))
|
||||
.layout(egui::Layout::right_to_left(egui::Align::Center)),
|
||||
);
|
||||
let (_, nv) =
|
||||
crate::widgets::hot_number(&mut vui, egui::Id::new(("lval", clip.0, id)), v, (soft_max - soft_min) / 300.0, (min, max), decimals as usize, "", t);
|
||||
if let Some(nv) = out.or(nv) {
|
||||
actions.push(json!({"clip": clip.0, "effect": idx, "param": id, "value": (nv * 10f64.powi(decimals as i32)).round() / 10f64.powi(decimals as i32)}));
|
||||
}
|
||||
@@ -274,8 +337,14 @@ fn curve_editor(ui: &mut egui::Ui, e: &EffectInstance, id: &str, hue: bool, col:
|
||||
p.rect_filled(area, 2.0, Color32::from_rgb(0x16, 0x16, 0x16));
|
||||
for i in 1..4 {
|
||||
let f = i as f32 / 4.0;
|
||||
p.line_segment([pos2(area.min.x + area.width() * f, area.min.y), pos2(area.min.x + area.width() * f, area.max.y)], Stroke::new(1.0, Color32::from_rgb(0x2a, 0x2a, 0x2a)));
|
||||
p.line_segment([pos2(area.min.x, area.min.y + area.height() * f), pos2(area.max.x, area.min.y + area.height() * f)], Stroke::new(1.0, Color32::from_rgb(0x2a, 0x2a, 0x2a)));
|
||||
p.line_segment(
|
||||
[pos2(area.min.x + area.width() * f, area.min.y), pos2(area.min.x + area.width() * f, area.max.y)],
|
||||
Stroke::new(1.0, Color32::from_rgb(0x2a, 0x2a, 0x2a)),
|
||||
);
|
||||
p.line_segment(
|
||||
[pos2(area.min.x, area.min.y + area.height() * f), pos2(area.max.x, area.min.y + area.height() * f)],
|
||||
Stroke::new(1.0, Color32::from_rgb(0x2a, 0x2a, 0x2a)),
|
||||
);
|
||||
}
|
||||
if hue {
|
||||
let n = 36;
|
||||
@@ -283,7 +352,14 @@ fn curve_editor(ui: &mut egui::Ui, e: &EffectInstance, id: &str, hue: bool, col:
|
||||
let f0 = i as f32 / n as f32;
|
||||
let rgb = filmcraft_color::hsl_to_rgb(f0, 0.8, 0.5);
|
||||
let c = Color32::from_rgb((rgb[0] * 255.0) as u8, (rgb[1] * 255.0) as u8, (rgb[2] * 255.0) as u8);
|
||||
p.rect_filled(Rect::from_min_max(pos2(area.min.x + area.width() * f0, area.max.y - 6.0), pos2(area.min.x + area.width() * (f0 + 1.0 / n as f32) + 0.5, area.max.y)), 0.0, c);
|
||||
p.rect_filled(
|
||||
Rect::from_min_max(
|
||||
pos2(area.min.x + area.width() * f0, area.max.y - 6.0),
|
||||
pos2(area.min.x + area.width() * (f0 + 1.0 / n as f32) + 0.5, area.max.y),
|
||||
),
|
||||
0.0,
|
||||
c,
|
||||
);
|
||||
}
|
||||
}
|
||||
let to_screen = |q: [f32; 2]| pos2(area.min.x + q[0] * area.width(), area.max.y - q[1] * area.height());
|
||||
@@ -315,7 +391,8 @@ fn curve_editor(ui: &mut egui::Ui, e: &EffectInstance, id: &str, hue: bool, col:
|
||||
if resp.drag_started()
|
||||
&& let Some(pos) = resp.interact_pointer_pos()
|
||||
{
|
||||
let near = pts.iter().enumerate().map(|(i, q)| (i, to_screen(*q).distance(pos))).filter(|(_, d)| *d < 9.0).min_by(|a, b| a.1.total_cmp(&b.1)).map(|x| x.0);
|
||||
let near =
|
||||
pts.iter().enumerate().map(|(i, q)| (i, to_screen(*q).distance(pos))).filter(|(_, d)| *d < 9.0).min_by(|a, b| a.1.total_cmp(&b.1)).map(|x| x.0);
|
||||
dragging = near.or_else(|| {
|
||||
new_pts.push(from_screen(pos));
|
||||
changed = true;
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
pub mod dialogs;
|
||||
pub mod effect_controls;
|
||||
pub mod effects;
|
||||
pub mod lumetri;
|
||||
pub mod export_mode;
|
||||
pub mod import_mode;
|
||||
pub mod lumetri;
|
||||
pub mod meters;
|
||||
pub mod misc;
|
||||
pub mod monitor;
|
||||
|
||||
+193
-1
@@ -1 +1,193 @@
|
||||
fn main() {}
|
||||
//! Workspace automation: `cargo xtask <layers|wasm|ci>`.
|
||||
//!
|
||||
//! - `layers`: enforces the dependency layering of `plan/architecture.md` §3 (downward-only edges,
|
||||
//! listed same-layer edges, L0 codec crates depend on `bitstream` only, no UI/OS crates below L5).
|
||||
//! - `wasm`: `cargo check --target wasm32-unknown-unknown` for every crate in L0–L4.
|
||||
//! - `ci`: fmt check, clippy -D warnings, tests, layers, wasm.
|
||||
|
||||
use std::process::{Command, ExitCode};
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
/// (crate name without the `filmcraft-` prefix, layer).
|
||||
const LAYERS: &[(&str, u8)] = &[
|
||||
("time", 0),
|
||||
("geom", 0),
|
||||
("color", 0),
|
||||
("bitstream", 0),
|
||||
("isobmff", 0),
|
||||
("matroska", 0),
|
||||
("riff", 0),
|
||||
("h264", 0),
|
||||
("h264enc", 0),
|
||||
("hevc", 0),
|
||||
("prores", 0),
|
||||
("mjpeg", 0),
|
||||
("dnx", 0),
|
||||
("aac", 0),
|
||||
("frame", 1),
|
||||
("media", 1),
|
||||
("project", 1),
|
||||
("audio-dsp", 1),
|
||||
("edit", 2),
|
||||
("codecs", 2),
|
||||
("keyframe", 2),
|
||||
("effects", 2),
|
||||
("text", 2),
|
||||
("audio", 2),
|
||||
("captions", 2),
|
||||
("interchange", 2),
|
||||
("render", 3),
|
||||
("gpu", 3),
|
||||
("scopes", 3),
|
||||
("playback", 3),
|
||||
("export", 3),
|
||||
("format", 3),
|
||||
("engine", 4),
|
||||
("platform", 5),
|
||||
("ui-egui", 5),
|
||||
("automation", 5),
|
||||
("filmcraft", 6),
|
||||
("cli", 6),
|
||||
];
|
||||
|
||||
/// L0 crates that are not codecs/containers (no `bitstream`-only restriction).
|
||||
const L0_FOUNDATION: &[&str] = &["time", "geom", "color", "bitstream"];
|
||||
|
||||
/// Allowed same-layer edges (from, to).
|
||||
const SAME_LAYER: &[(&str, &str)] = &[
|
||||
("media", "frame"),
|
||||
("project", "media"),
|
||||
("project", "frame"),
|
||||
("effects", "keyframe"),
|
||||
("audio", "keyframe"),
|
||||
("playback", "render"),
|
||||
("playback", "gpu"),
|
||||
("export", "render"),
|
||||
("scopes", "gpu"),
|
||||
("gpu", "render"),
|
||||
("cli", "filmcraft"),
|
||||
];
|
||||
|
||||
/// Crates that must not appear below L5 (UI toolkits, windowing, OS audio/menus).
|
||||
const UI_ONLY: &[&str] = &["egui", "eframe", "egui-wgpu", "winit", "rfd", "cpal", "muda"];
|
||||
|
||||
fn short(name: &str) -> &str {
|
||||
name.strip_prefix("filmcraft-").unwrap_or(name)
|
||||
}
|
||||
|
||||
fn layer_of(name: &str) -> Option<u8> {
|
||||
LAYERS.iter().find(|(n, _)| *n == short(name)).map(|(_, l)| *l)
|
||||
}
|
||||
|
||||
fn metadata() -> Result<Value, String> {
|
||||
let out = Command::new(env!("CARGO")).args(["metadata", "--format-version", "1", "--no-deps"]).output().map_err(|e| e.to_string())?;
|
||||
if !out.status.success() {
|
||||
return Err(String::from_utf8_lossy(&out.stderr).into());
|
||||
}
|
||||
serde_json::from_slice(&out.stdout).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn workspace_crates(md: &Value) -> Vec<(String, Vec<String>)> {
|
||||
md["packages"]
|
||||
.as_array()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter(|p| p["name"] != "xtask")
|
||||
.map(|p| {
|
||||
let deps = p["dependencies"]
|
||||
.as_array()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter(|d| d["kind"].is_null() || d["kind"] == "build")
|
||||
.filter_map(|d| d["name"].as_str().map(str::to_string))
|
||||
.collect();
|
||||
(p["name"].as_str().unwrap_or_default().to_string(), deps)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn layers() -> Result<(), String> {
|
||||
let md = metadata()?;
|
||||
let mut errors = Vec::new();
|
||||
for (name, deps) in workspace_crates(&md) {
|
||||
let Some(l) = layer_of(&name) else {
|
||||
errors.push(format!("{name}: not assigned a layer (add it to xtask LAYERS and plan/architecture.md §3)"));
|
||||
continue;
|
||||
};
|
||||
for d in &deps {
|
||||
if l < 5 && UI_ONLY.contains(&d.as_str()) {
|
||||
errors.push(format!("{name} (L{l}) depends on UI/OS crate `{d}`"));
|
||||
}
|
||||
if !d.starts_with("filmcraft-") {
|
||||
continue;
|
||||
}
|
||||
let Some(dl) = layer_of(d) else { continue };
|
||||
let (a, b) = (short(&name), short(d));
|
||||
if l == 0 && !L0_FOUNDATION.contains(&a) && b != "bitstream" {
|
||||
errors.push(format!("{a} (L0 codec/container) may depend only on bitstream, found {b}"));
|
||||
} else if dl > l {
|
||||
errors.push(format!("{a} (L{l}) depends upward on {b} (L{dl})"));
|
||||
} else if dl == l && l > 0 && !SAME_LAYER.contains(&(a, b)) {
|
||||
errors.push(format!("{a} → {b}: same-layer edge (L{l}) not in the allowed list"));
|
||||
}
|
||||
}
|
||||
}
|
||||
if errors.is_empty() {
|
||||
println!("layers: ok");
|
||||
Ok(())
|
||||
} else {
|
||||
Err(errors.join("\n"))
|
||||
}
|
||||
}
|
||||
|
||||
fn run(cmd: &mut Command) -> Result<(), String> {
|
||||
eprintln!("$ {cmd:?}");
|
||||
let st = cmd.status().map_err(|e| e.to_string())?;
|
||||
if st.success() { Ok(()) } else { Err(format!("failed: {cmd:?}")) }
|
||||
}
|
||||
|
||||
fn wasm() -> Result<(), String> {
|
||||
let md = metadata()?;
|
||||
let mut cmd = Command::new(env!("CARGO"));
|
||||
cmd.args(["check", "--target", "wasm32-unknown-unknown"]);
|
||||
let mut n = 0;
|
||||
for (name, _) in workspace_crates(&md) {
|
||||
if layer_of(&name).is_some_and(|l| l <= 4) {
|
||||
cmd.args(["-p", &name]);
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
if n == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
run(&mut cmd)?;
|
||||
println!("wasm: ok ({n} crates)");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ci() -> Result<(), String> {
|
||||
let cargo = env!("CARGO");
|
||||
run(Command::new(cargo).args(["fmt", "--check"]))?;
|
||||
run(Command::new(cargo).args(["clippy", "--workspace", "--all-targets", "--release", "--", "-D", "warnings"]))?;
|
||||
run(Command::new(cargo).args(["test", "--workspace", "--release"]))?;
|
||||
layers()?;
|
||||
wasm()
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let task = std::env::args().nth(1).unwrap_or_default();
|
||||
let r = match task.as_str() {
|
||||
"layers" => layers(),
|
||||
"wasm" => wasm(),
|
||||
"ci" => ci(),
|
||||
_ => Err("usage: cargo xtask <layers|wasm|ci>".into()),
|
||||
};
|
||||
match r {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user