mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-24 10:09:32 -06:00
panels: the render queue, effects and the remaining panels read their text from the catalog
Render queue settings, templates and progress, the effect and preset panels, Essential Graphics, precomposing, the tutorials, the credits and the info/scope readouts now go through `crate::i18n`. Units (`px`, `%`, `mm`, `s`) and the automation ids and labels stay English on purpose: they are read by scripts and tests, not by people.
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
//! build.rs from contributors/contributors.json (craftrules standards/contributors.md); nothing is
|
||||
//! read at run time.
|
||||
|
||||
use crate::i18n::{tr, tr_args};
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use egui::RichText;
|
||||
@@ -243,7 +244,7 @@ pub fn contributors_ui(ui: &mut egui::Ui, auto: &mut Registry) {
|
||||
let id = egui::Id::new("credits_view");
|
||||
let mut v = ui.data_mut(|d| d.get_temp::<View>(id)).unwrap_or_default();
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.label("Show");
|
||||
ui.label(tr("Show"));
|
||||
for m in NameMode::ALL {
|
||||
let r = ui.selectable_label(v.names == m, m.label());
|
||||
auto.add(&format!("about.credits.show.{}", m.id()), r.rect, m.label());
|
||||
@@ -252,7 +253,7 @@ pub fn contributors_ui(ui: &mut egui::Ui, auto: &mut Registry) {
|
||||
}
|
||||
}
|
||||
ui.separator();
|
||||
ui.label("Sort");
|
||||
ui.label(tr("Sort"));
|
||||
let combo = egui::ComboBox::from_id_salt("credits_sort").selected_text(v.key.label().0).show_ui(ui, |ui| {
|
||||
for k in SortKey::ALL {
|
||||
let r = ui.selectable_label(v.key == k, k.label().0);
|
||||
@@ -264,29 +265,29 @@ pub fn contributors_ui(ui: &mut egui::Ui, auto: &mut Registry) {
|
||||
}
|
||||
});
|
||||
auto.add("about.credits.sort", combo.response.rect, "Sort");
|
||||
let r = ui.button(if v.ascending { "▲" } else { "▼" }).on_hover_text("Reverse the order");
|
||||
let r = ui.button(if v.ascending { "▲" } else { "▼" }).on_hover_text(tr("Reverse the order"));
|
||||
auto.add("about.credits.reverse", r.rect, "Reverse the order");
|
||||
if r.clicked() {
|
||||
v.ascending = !v.ascending;
|
||||
}
|
||||
ui.separator();
|
||||
let r = ui.selectable_label(!v.table, "Grab bag");
|
||||
let r = ui.selectable_label(!v.table, tr("Grab bag"));
|
||||
auto.add("about.credits.view.grabBag", r.rect, "Grab bag");
|
||||
if r.clicked() {
|
||||
v.table = false;
|
||||
}
|
||||
let r = ui.selectable_label(v.table, "Table");
|
||||
let r = ui.selectable_label(v.table, tr("Table"));
|
||||
auto.add("about.credits.view.table", r.rect, "Table");
|
||||
if r.clicked() {
|
||||
v.table = true;
|
||||
}
|
||||
});
|
||||
let list = sorted(CONTRIBUTORS, v.names, v.key, v.ascending);
|
||||
ui.label(RichText::new(format!("{} contributors · {} commits", list.len(), group(TOTAL_COMMITS))).small().weak());
|
||||
ui.label(RichText::new(tr_args("{} contributors · {} commits", &[&(list.len()), &(group(TOTAL_COMMITS))])).small().weak());
|
||||
ui.separator();
|
||||
egui::ScrollArea::both().auto_shrink([false, false]).show(ui, |ui| {
|
||||
if list.is_empty() {
|
||||
ui.label("No contributor data was built into this copy.");
|
||||
ui.label(tr("No contributor data was built into this copy."));
|
||||
} else if v.table {
|
||||
table(ui, &list, &mut v, auto);
|
||||
} else {
|
||||
@@ -340,7 +341,7 @@ fn table(ui: &mut egui::Ui, list: &[&Contributor], v: &mut View, auto: &mut Regi
|
||||
/// About ▸ Models: AI models credited in Co-Authored-By trailers.
|
||||
pub fn models_ui(ui: &mut egui::Ui) {
|
||||
if MODELS.is_empty() {
|
||||
ui.label("No model credits were built into this copy.");
|
||||
ui.label(tr("No model credits were built into this copy."));
|
||||
return;
|
||||
}
|
||||
let assisted: u64 = MODELS.iter().map(|m| m.commits).max().unwrap_or(0).max(1);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
//! Basic / Advanced / 3D Renderer tabs, Cancel / OK. Every control registers an automation id
|
||||
//! (`dialog.comp.<field>`).
|
||||
|
||||
use crate::i18n::{tr, tr_args};
|
||||
use effectcraft_engine::time::{FrameRate, format_timecode_frames, parse_timecode};
|
||||
use egui::{Color32, vec2};
|
||||
use serde_json::json;
|
||||
@@ -174,7 +175,7 @@ fn timecode_field(ui: &mut egui::Ui, buf: &mut Option<String>, secs: &mut f64, r
|
||||
pub fn show(ui: &mut egui::Ui, d: &mut CompDraft, t: &Tokens, auto: &mut Registry) -> Option<serde_json::Value> {
|
||||
let mut out = None;
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Composition Name:");
|
||||
ui.label(tr("Composition Name:"));
|
||||
let r = ui.add(egui::TextEdit::singleline(&mut d.name).desired_width(300.0));
|
||||
auto.add("dialog.comp.name", r.rect, "Composition Name");
|
||||
});
|
||||
@@ -193,7 +194,7 @@ pub fn show(ui: &mut egui::Ui, d: &mut CompDraft, t: &Tokens, auto: &mut Registr
|
||||
match d.tab {
|
||||
Tab::Basic => {
|
||||
egui::Grid::new("comp-basic").num_columns(2).spacing([12.0, 9.0]).show(ui, |ui| {
|
||||
ui.label("Preset:");
|
||||
ui.label(tr("Preset:"));
|
||||
let current = PRESETS
|
||||
.iter()
|
||||
.find(|(_, w, h, par, f)| *w == d.width && *h == d.height && (par - d.pixel_aspect).abs() < 1e-3 && (f - d.fps).abs() < 1e-3)
|
||||
@@ -208,7 +209,7 @@ pub fn show(ui: &mut egui::Ui, d: &mut CompDraft, t: &Tokens, auto: &mut Registr
|
||||
});
|
||||
auto.add("dialog.comp.preset", r.response.rect, "Preset");
|
||||
ui.end_row();
|
||||
ui.label("Width:");
|
||||
ui.label(tr("Width:"));
|
||||
ui.horizontal(|ui| {
|
||||
let (ow, oh) = (d.width, d.height);
|
||||
let r = ui.add(egui::DragValue::new(&mut d.width).range(4..=30000).suffix(" px"));
|
||||
@@ -221,7 +222,7 @@ pub fn show(ui: &mut egui::Ui, d: &mut CompDraft, t: &Tokens, auto: &mut Registr
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
ui.label("Height:");
|
||||
ui.label(tr("Height:"));
|
||||
let (ow, oh) = (d.width, d.height);
|
||||
let r = ui.add(egui::DragValue::new(&mut d.height).range(4..=30000).suffix(" px"));
|
||||
auto.add("dialog.comp.height", r.rect, "Height");
|
||||
@@ -229,7 +230,7 @@ pub fn show(ui: &mut egui::Ui, d: &mut CompDraft, t: &Tokens, auto: &mut Registr
|
||||
d.width = ((d.height as f64) * ow as f64 / oh as f64).round().max(4.0) as u32;
|
||||
}
|
||||
ui.end_row();
|
||||
ui.label("Pixel Aspect Ratio:");
|
||||
ui.label(tr("Pixel Aspect Ratio:"));
|
||||
ui.horizontal(|ui| {
|
||||
let cur = PIXEL_ASPECTS
|
||||
.iter()
|
||||
@@ -245,12 +246,12 @@ pub fn show(ui: &mut egui::Ui, d: &mut CompDraft, t: &Tokens, auto: &mut Registr
|
||||
});
|
||||
auto.add("dialog.comp.pixelAspect", r.response.rect, "Pixel Aspect Ratio");
|
||||
ui.label(
|
||||
egui::RichText::new(format!("Frame Aspect Ratio: {}", aspect_label(d.width as f64 * d.pixel_aspect, d.height as f64)))
|
||||
egui::RichText::new(tr_args("Frame Aspect Ratio: {}", &[&(aspect_label(d.width as f64 * d.pixel_aspect, d.height as f64))]))
|
||||
.color(Color32::GRAY),
|
||||
);
|
||||
});
|
||||
ui.end_row();
|
||||
ui.label("Frame Rate:");
|
||||
ui.label(tr("Frame Rate:"));
|
||||
ui.horizontal(|ui| {
|
||||
// Any rate can be typed; the list holds the common ones.
|
||||
let r = ui.add(egui::DragValue::new(&mut d.fps).range(1.0..=999.0).speed(0.01).max_decimals(3));
|
||||
@@ -263,25 +264,25 @@ pub fn show(ui: &mut egui::Ui, d: &mut CompDraft, t: &Tokens, auto: &mut Registr
|
||||
}
|
||||
});
|
||||
auto.add("dialog.comp.frameRates", r.response.rect, "Frame Rate presets");
|
||||
ui.label("frames per second");
|
||||
ui.label(tr("frames per second"));
|
||||
if rate.supports_drop_frame() {
|
||||
ui.label(egui::RichText::new("Drop Frame").color(Color32::GRAY));
|
||||
ui.label(egui::RichText::new(tr("Drop Frame")).color(Color32::GRAY));
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
ui.label("");
|
||||
let mb = d.width as f64 * d.height as f64 * 4.0 / (1024.0 * 1024.0);
|
||||
ui.label(egui::RichText::new(format!("{} x {}, {mb:.1}MB per 8bpc frame", d.width, d.height)).color(Color32::GRAY));
|
||||
ui.label(egui::RichText::new(tr_args("{} x {}, {}MB per 8bpc frame", &[&d.width, &d.height, &format!("{mb:.1}")])).color(Color32::GRAY));
|
||||
ui.end_row();
|
||||
ui.label("Start Timecode:");
|
||||
ui.label(tr("Start Timecode:"));
|
||||
let r = ui.horizontal(|ui| timecode_field(ui, &mut d.start_tc, &mut d.start, rate, i64::MIN / 4)).inner;
|
||||
auto.add("dialog.comp.startTimecode", r.rect, "Start Timecode");
|
||||
ui.end_row();
|
||||
ui.label("Duration:");
|
||||
ui.label(tr("Duration:"));
|
||||
let r = ui.horizontal(|ui| timecode_field(ui, &mut d.dur_tc, &mut d.duration, rate, 1)).inner;
|
||||
auto.add("dialog.comp.duration", r.rect, "Duration");
|
||||
ui.end_row();
|
||||
ui.label("Background Color:");
|
||||
ui.label(tr("Background Color:"));
|
||||
ui.horizontal(|ui| {
|
||||
let r = crate::widgets::srgb_color_button(ui, &mut d.bg);
|
||||
auto.add("dialog.comp.background", r.rect, "Background Color");
|
||||
@@ -292,7 +293,7 @@ pub fn show(ui: &mut egui::Ui, d: &mut CompDraft, t: &Tokens, auto: &mut Registr
|
||||
}
|
||||
Tab::Advanced => {
|
||||
egui::Grid::new("comp-adv").num_columns(2).spacing([12.0, 9.0]).show(ui, |ui| {
|
||||
ui.label("Anchor:");
|
||||
ui.label(tr("Anchor:"));
|
||||
egui::Grid::new("comp-anchor").spacing([2.0, 2.0]).show(ui, |ui| {
|
||||
for i in 0..9u8 {
|
||||
let on = d.anchor == i;
|
||||
@@ -309,28 +310,28 @@ pub fn show(ui: &mut egui::Ui, d: &mut CompDraft, t: &Tokens, auto: &mut Registr
|
||||
});
|
||||
ui.end_row();
|
||||
ui.label("");
|
||||
let r = ui.checkbox(&mut d.preserve_frame_rate, "Preserve frame rate when nested or in render queue");
|
||||
let r = ui.checkbox(&mut d.preserve_frame_rate, tr("Preserve frame rate when nested or in render queue"));
|
||||
auto.add("dialog.comp.preserveFrameRate", r.rect, "Preserve frame rate when nested or in render queue");
|
||||
ui.end_row();
|
||||
ui.label("");
|
||||
let r = ui.checkbox(&mut d.preserve_resolution, "Preserve resolution when nested");
|
||||
let r = ui.checkbox(&mut d.preserve_resolution, tr("Preserve resolution when nested"));
|
||||
auto.add("dialog.comp.preserveResolution", r.rect, "Preserve resolution when nested");
|
||||
ui.end_row();
|
||||
ui.label(egui::RichText::new("Motion Blur").strong());
|
||||
ui.label(egui::RichText::new(tr("Motion Blur")).strong());
|
||||
ui.end_row();
|
||||
ui.label("Shutter Angle:");
|
||||
ui.label(tr("Shutter Angle:"));
|
||||
let r = ui.add(egui::DragValue::new(&mut d.shutter_angle).range(0.0..=720.0).suffix("°"));
|
||||
auto.add("dialog.comp.shutterAngle", r.rect, "Shutter Angle");
|
||||
ui.end_row();
|
||||
ui.label("Shutter Phase:");
|
||||
ui.label(tr("Shutter Phase:"));
|
||||
let r = ui.add(egui::DragValue::new(&mut d.shutter_phase).range(-360.0..=360.0).suffix("°"));
|
||||
auto.add("dialog.comp.shutterPhase", r.rect, "Shutter Phase");
|
||||
ui.end_row();
|
||||
ui.label("Samples Per Frame:");
|
||||
ui.label(tr("Samples Per Frame:"));
|
||||
let r = ui.add(egui::DragValue::new(&mut d.samples).range(2..=64));
|
||||
auto.add("dialog.comp.samples", r.rect, "Samples Per Frame");
|
||||
ui.end_row();
|
||||
ui.label("Adaptive Sample Limit:");
|
||||
ui.label(tr("Adaptive Sample Limit:"));
|
||||
let r = ui.add(egui::DragValue::new(&mut d.adaptive_limit).range(16..=256));
|
||||
auto.add("dialog.comp.adaptiveLimit", r.rect, "Adaptive Sample Limit");
|
||||
ui.end_row();
|
||||
@@ -338,11 +339,11 @@ pub fn show(ui: &mut egui::Ui, d: &mut CompDraft, t: &Tokens, auto: &mut Registr
|
||||
}
|
||||
Tab::Renderer => {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Renderer:");
|
||||
ui.label(tr("Renderer:"));
|
||||
let r =
|
||||
egui::ComboBox::from_id_salt("comp-renderer").selected_text(if d.advanced_3d { "Advanced 3D" } else { "Classic 3D" }).show_ui(ui, |ui| {
|
||||
ui.selectable_value(&mut d.advanced_3d, false, "Classic 3D");
|
||||
ui.selectable_value(&mut d.advanced_3d, true, "Advanced 3D");
|
||||
ui.selectable_value(&mut d.advanced_3d, false, tr("Classic 3D"));
|
||||
ui.selectable_value(&mut d.advanced_3d, true, tr("Advanced 3D"));
|
||||
});
|
||||
auto.add("dialog.comp.renderer", r.response.rect, "Renderer");
|
||||
});
|
||||
@@ -351,12 +352,12 @@ pub fn show(ui: &mut egui::Ui, d: &mut CompDraft, t: &Tokens, auto: &mut Registr
|
||||
ui.add_space(14.0);
|
||||
ui.horizontal(|ui| {
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
let r = ui.add(egui::Button::new(egui::RichText::new(" OK ").color(Color32::WHITE)).fill(t.accent));
|
||||
let r = ui.add(egui::Button::new(egui::RichText::new(tr(" OK ")).color(Color32::WHITE)).fill(t.accent));
|
||||
auto.add("dialog.comp.ok", r.rect, "OK");
|
||||
if r.clicked() {
|
||||
out = Some(params(d));
|
||||
}
|
||||
let r = ui.button("Cancel");
|
||||
let r = ui.button(tr("Cancel"));
|
||||
auto.add("dialog.comp.cancel", r.rect, "Cancel");
|
||||
if r.clicked() {
|
||||
out = Some(serde_json::Value::Null);
|
||||
|
||||
@@ -286,7 +286,9 @@ fn prop_row(
|
||||
if matches!(prop.ui, ParamUi::Point | ParamUi::Point3) {
|
||||
let cr = Rect::from_center_size(pos2(x + 8.0, cy), vec2(18.0, 18.0));
|
||||
let active = app.ui.fx_pick.as_ref().is_some_and(|k| k.prop == uid && k.kind == "point");
|
||||
if fw::crosshair_button(ui, cr, active, egui::Id::new(("ec-xh", uid)), &t).on_hover_text("Click, then click in the Composition panel").clicked()
|
||||
if fw::crosshair_button(ui, cr, active, egui::Id::new(("ec-xh", uid)), &t)
|
||||
.on_hover_text(crate::i18n::tr("Click, then click in the Composition panel"))
|
||||
.clicked()
|
||||
{
|
||||
toggle_pick(app, "point", layer, prop);
|
||||
}
|
||||
@@ -314,7 +316,7 @@ fn prop_row(
|
||||
let er = Rect::from_center_size(pos2(sr.max.x + 14.0, cy), vec2(18.0, 18.0));
|
||||
let active = app.ui.fx_pick.as_ref().is_some_and(|k| k.prop == uid && k.kind == "color");
|
||||
if fw::eyedropper_button(ui, er, active, egui::Id::new(("ec-eye", uid)), &t)
|
||||
.on_hover_text("Eyedropper: click, then click in the Composition panel")
|
||||
.on_hover_text(crate::i18n::tr("Eyedropper: click, then click in the Composition panel"))
|
||||
.clicked()
|
||||
{
|
||||
toggle_pick(app, "color", layer, prop);
|
||||
@@ -711,7 +713,7 @@ fn channel_popup(
|
||||
auto: &str,
|
||||
) -> Option<usize> {
|
||||
let t = app.tokens;
|
||||
p.text(pos2(x, cy), Align2::LEFT_CENTER, "Channel:", Tokens::ui(12.0), t.text_dim);
|
||||
p.text(pos2(x, cy), Align2::LEFT_CENTER, crate::i18n::tr("Channel:"), Tokens::ui(12.0), t.text_dim);
|
||||
let dr = Rect::from_min_size(pos2(x + 58.0, cy - 9.0), vec2(80.0, 18.0));
|
||||
let pop = id.with("pop");
|
||||
if widgets::dropdown(ui, dr, names.get(cur).copied().unwrap_or(""), &t, id).clicked() {
|
||||
@@ -764,7 +766,7 @@ fn curves_editor(app: &mut EffectcraftApp, ui: &mut egui::Ui, p: &egui::Painter,
|
||||
};
|
||||
let rr = Rect::from_min_size(pos2(mr.max.x + 8.0, cy - 9.0), vec2(44.0, 18.0));
|
||||
let rresp = ui.interact(rr, egui::Id::new(("ec-creset", euid)), Sense::click());
|
||||
p.text(rr.center(), Align2::CENTER_CENTER, "Reset", Tokens::ui(11.5), if rresp.hovered() { t.hot_text } else { t.text_dim });
|
||||
p.text(rr.center(), Align2::CENTER_CENTER, crate::i18n::tr("Reset"), Tokens::ui(11.5), if rresp.hovered() { t.hot_text } else { t.text_dim });
|
||||
app.auto.add(&format!("effectControls.effect.{euid}.curves.reset"), rr, "Reset curve");
|
||||
if rresp.clicked() {
|
||||
actions.push(("prop.set".into(), json!({"layer": layer.id.0, "prop": prop.uid, "value": fw::CurvePoints::identity().format()})));
|
||||
@@ -833,7 +835,7 @@ fn levels_editor(
|
||||
app.ui.fx_levels_channel.insert(euid, n);
|
||||
}
|
||||
} else {
|
||||
p.text(pos2(x0, cy), Align2::LEFT_CENTER, "Histogram", Tokens::ui(12.0), t.text_dim);
|
||||
p.text(pos2(x0, cy), Align2::LEFT_CENTER, crate::i18n::tr("Histogram"), Tokens::ui(12.0), t.text_dim);
|
||||
}
|
||||
let w = (r.width() - 72.0).clamp(120.0, 256.0);
|
||||
let hr = Rect::from_min_size(pos2(x0 + 8.0, r.min.y + 34.0), vec2(w, 80.0));
|
||||
@@ -898,7 +900,7 @@ fn levels_editor(
|
||||
fn histogram_only(app: &mut EffectcraftApp, ui: &mut egui::Ui, p: &egui::Painter, g: &PropGroup, r: Rect) {
|
||||
let t = app.tokens;
|
||||
let x0 = r.min.x + 30.0;
|
||||
p.text(pos2(x0, r.min.y + 12.0), Align2::LEFT_CENTER, "Histogram", Tokens::ui(12.0), t.text_dim);
|
||||
p.text(pos2(x0, r.min.y + 12.0), Align2::LEFT_CENTER, crate::i18n::tr("Histogram"), Tokens::ui(12.0), t.text_dim);
|
||||
let w = (r.width() - 72.0).clamp(120.0, 256.0);
|
||||
let hr = Rect::from_min_size(pos2(x0 + 8.0, r.min.y + 24.0), vec2(w, 80.0));
|
||||
match frame_histogram(app, ui.ctx()) {
|
||||
@@ -921,7 +923,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
let p = ui.painter().with_clip_rect(rect);
|
||||
let ctx = ui.ctx().clone();
|
||||
let Some(layer) = selected_layer(app) else {
|
||||
p.text(rect.center(), Align2::CENTER_CENTER, "Select a layer to see its effects", Tokens::ui(12.0), t.text_faint);
|
||||
p.text(rect.center(), Align2::CENTER_CENTER, crate::i18n::tr("Select a layer to see its effects"), Tokens::ui(12.0), t.text_faint);
|
||||
return;
|
||||
};
|
||||
let Some(cid) = app.session.active_comp_id() else { return };
|
||||
@@ -986,7 +988,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
bp.text(pos2(tw.max.x + 6.0, r.center().y), Align2::LEFT_CENTER, &g.name, Tokens::semibold(12.0), t.text);
|
||||
let reset = Rect::from_min_size(pos2(r.max.x - 96.0, r.min.y + 4.0), vec2(40.0, 18.0));
|
||||
let rresp = ui.interact(reset, egui::Id::new(("ec-reset", g.uid)), Sense::click());
|
||||
bp.text(reset.center(), Align2::CENTER_CENTER, "Reset", Tokens::ui(11.5), if rresp.hovered() { t.hot_text } else { t.text_dim });
|
||||
bp.text(reset.center(), Align2::CENTER_CENTER, crate::i18n::tr("Reset"), Tokens::ui(11.5), if rresp.hovered() { t.hot_text } else { t.text_dim });
|
||||
app.auto.add(&format!("effectControls.effect.{}.reset", g.uid), reset, "Reset");
|
||||
if rresp.clicked() {
|
||||
actions.push(("effect.reset".into(), json!({"layer": layer.id.0, "effect": g.uid})));
|
||||
@@ -994,7 +996,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
// About…: the effect's name and category (AE's About dialog).
|
||||
let about = Rect::from_min_size(pos2(r.max.x - 50.0, r.min.y + 4.0), vec2(44.0, 18.0));
|
||||
let aresp = ui.interact(about, egui::Id::new(("ec-about", g.uid)), Sense::click());
|
||||
bp.text(about.center(), Align2::CENTER_CENTER, "About...", Tokens::ui(11.5), if aresp.hovered() { t.hot_text } else { t.text_dim });
|
||||
bp.text(about.center(), Align2::CENTER_CENTER, crate::i18n::tr("About..."), Tokens::ui(11.5), if aresp.hovered() { t.hot_text } else { t.text_dim });
|
||||
app.auto.add(&format!("effectControls.effect.{}.about", g.uid), about, "About");
|
||||
let apop = egui::Id::new(("ec-about-pop", g.uid));
|
||||
if aresp.clicked() {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
//! selected layers; dragging an effect onto a layer (timeline, viewer, Effect Controls) applies
|
||||
//! it there.
|
||||
|
||||
use crate::i18n::tr;
|
||||
use egui::{Align2, Color32, Rect, Sense, Stroke, pos2, vec2};
|
||||
use serde_json::json;
|
||||
|
||||
@@ -276,7 +277,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
// Contents menu.
|
||||
let mr = Rect::from_center_size(pos2(rect.max.x - 18.0, sr.center().y), vec2(22.0, 22.0));
|
||||
let mpop = egui::Id::new("fx-contents-menu");
|
||||
if widgets::icon_button(ui, mr, Icon::Hamburger, false, &t, egui::Id::new("fx-menu")).on_hover_text("Effects & Presets options").clicked() {
|
||||
if widgets::icon_button(ui, mr, Icon::Hamburger, false, &t, egui::Id::new("fx-menu")).on_hover_text(tr("Effects & Presets options")).clicked() {
|
||||
widgets::open_popup(ui, mpop);
|
||||
}
|
||||
app.auto.add("effects.menu", mr, "Effects & Presets options");
|
||||
@@ -394,7 +395,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
}
|
||||
}
|
||||
if y == list.min.y - scroll.offset && searching {
|
||||
lp.text(pos2(list.center().x, list.min.y + 24.0), Align2::CENTER_CENTER, "No matching effects or presets", Tokens::ui(12.0), t.text_faint);
|
||||
lp.text(pos2(list.center().x, list.min.y + 24.0), Align2::CENTER_CENTER, tr("No matching effects or presets"), Tokens::ui(12.0), t.text_faint);
|
||||
}
|
||||
let content = y + scroll.offset - list.min.y;
|
||||
scroll.end(ui, &mut app.auto, "effects.scroll", content, &t);
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
//! id (`essential.primary`, `essential.solo`, `essential.name`, `essential.control.<id>.value`,
|
||||
//! `essential.instance.<id>.value`, `essential.export`…).
|
||||
|
||||
use crate::i18n::{tr, tr_args};
|
||||
use effectcraft_engine::keyframe::Value as KV;
|
||||
use effectcraft_engine::project::essential::{self, ControlType, EgControl, EgKind};
|
||||
use effectcraft_engine::project::{ItemId, ItemKind, LayerSource, ParamUi, Property};
|
||||
@@ -61,7 +62,7 @@ fn body(app: &mut EffectcraftApp, ui: &mut egui::Ui) -> (Actions, Actions) {
|
||||
let mut actions: Actions = vec![];
|
||||
let mut invoke: Vec<(String, Value)> = vec![];
|
||||
let Some(cid) = primary(app) else {
|
||||
ui.label(RichText::new("Open a composition to build Essential Graphics").color(t.text_faint));
|
||||
ui.label(RichText::new(tr("Open a composition to build Essential Graphics")).color(t.text_faint));
|
||||
return (actions, invoke);
|
||||
};
|
||||
let project = app.session.project.clone();
|
||||
@@ -69,7 +70,7 @@ fn body(app: &mut EffectcraftApp, ui: &mut egui::Ui) -> (Actions, Actions) {
|
||||
|
||||
// ---- Primary + Solo.
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(RichText::new("Primary:").color(t.text_dim));
|
||||
ui.label(RichText::new(tr("Primary:")).color(t.text_dim));
|
||||
let r = egui::ComboBox::from_id_salt("eg-primary").selected_text(&comp_name).width(150.0).show_ui(ui, |ui| {
|
||||
for (id, _) in project.comps() {
|
||||
let name = project.item(*id).map(|i| i.name.clone()).unwrap_or_default();
|
||||
@@ -81,7 +82,7 @@ fn body(app: &mut EffectcraftApp, ui: &mut egui::Ui) -> (Actions, Actions) {
|
||||
app.auto.add("essential.primary", r.response.rect, &comp_name);
|
||||
});
|
||||
let solo = app.session.state.essential_solo;
|
||||
let r = ui.add(egui::Button::new("Solo Supported Properties").selected(solo));
|
||||
let r = ui.add(egui::Button::new(tr("Solo Supported Properties")).selected(solo));
|
||||
app.auto.add("essential.solo", r.rect, "Solo Supported Properties");
|
||||
if r.clicked() {
|
||||
actions.push(("essential.soloSupported".into(), json!({"on": !solo})));
|
||||
@@ -91,7 +92,7 @@ fn body(app: &mut EffectcraftApp, ui: &mut egui::Ui) -> (Actions, Actions) {
|
||||
let shown_name = if eg.name.is_empty() { comp_name.clone() } else { eg.name.clone() };
|
||||
let name_id = egui::Id::new("eg-name-buf");
|
||||
let mut name: String = ctx.data(|d| d.get_temp(name_id)).unwrap_or_else(|| shown_name.clone());
|
||||
let r = ui.add(egui::TextEdit::singleline(&mut name).hint_text("Template name").desired_width(f32::INFINITY));
|
||||
let r = ui.add(egui::TextEdit::singleline(&mut name).hint_text(tr("Template name")).desired_width(f32::INFINITY));
|
||||
app.auto.add("essential.name", r.rect, &shown_name);
|
||||
if r.has_focus() {
|
||||
ctx.data_mut(|d| d.insert_temp(name_id, name.clone()));
|
||||
@@ -107,7 +108,7 @@ fn body(app: &mut EffectcraftApp, ui: &mut egui::Ui) -> (Actions, Actions) {
|
||||
let list = ui.scope(|ui| {
|
||||
ui.set_min_height(60.0);
|
||||
if eg.controls.is_empty() {
|
||||
ui.label(RichText::new("Drag properties here from the timeline,\nor use Animation > Add Property to Essential Graphics.").color(t.text_faint));
|
||||
ui.label(RichText::new(tr("Drag properties here from the timeline,\nor use Animation > Add Property to Essential Graphics.")).color(t.text_faint));
|
||||
}
|
||||
controls(app, ui, cid, &eg.controls, None, &mut actions);
|
||||
});
|
||||
@@ -128,12 +129,12 @@ fn body(app: &mut EffectcraftApp, ui: &mut egui::Ui) -> (Actions, Actions) {
|
||||
|
||||
// ---- Bottom buttons.
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
let r = ui.menu_button("Add Formatting", |ui| {
|
||||
if ui.button("Add Group").clicked() {
|
||||
let r = ui.menu_button(tr("Add Formatting"), |ui| {
|
||||
if ui.button(tr("Add Group")).clicked() {
|
||||
actions.push(("essential.addGroup".into(), json!({"comp": cid.0})));
|
||||
ui.close();
|
||||
}
|
||||
if ui.button("Add Comment").clicked() {
|
||||
if ui.button(tr("Add Comment")).clicked() {
|
||||
actions.push(("essential.addComment".into(), json!({"comp": cid.0})));
|
||||
ui.close();
|
||||
}
|
||||
@@ -142,7 +143,7 @@ fn body(app: &mut EffectcraftApp, ui: &mut egui::Ui) -> (Actions, Actions) {
|
||||
// The selected property as a Font control (Source Text) or a uniform Scale slider.
|
||||
let has_sel = !app.session.state.selected_props.is_empty();
|
||||
let r = ui.add_enabled_ui(has_sel, |ui| {
|
||||
ui.menu_button("Add Selected As", |ui| {
|
||||
ui.menu_button(tr("Add Selected As"), |ui| {
|
||||
for (label, as_type) in [("Font Control", "font"), ("Uniform Scale", "scale")] {
|
||||
if ui.button(label).clicked() {
|
||||
actions.push(("essential.addProperty".into(), json!({"as": as_type})));
|
||||
@@ -155,14 +156,14 @@ fn body(app: &mut EffectcraftApp, ui: &mut egui::Ui) -> (Actions, Actions) {
|
||||
let footage_layer = app.session.active_comp().and_then(|c| {
|
||||
app.session.state.selected_layers.iter().filter_map(|l| c.layer(*l)).find(|l| matches!(l.source, LayerSource::Footage { .. })).map(|l| l.id.0)
|
||||
});
|
||||
let r = ui.add_enabled(footage_layer.is_some(), egui::Button::new("Media Replacement"));
|
||||
let r = ui.add_enabled(footage_layer.is_some(), egui::Button::new(tr("Media Replacement")));
|
||||
app.auto.add("essential.addMedia", r.rect, "Add Media Replacement");
|
||||
if r.clicked()
|
||||
&& let Some(l) = footage_layer
|
||||
{
|
||||
actions.push(("essential.addMedia".into(), json!({"layer": l})));
|
||||
}
|
||||
let r = ui.add_enabled(!eg.controls.is_empty(), egui::Button::new("Export Template…"));
|
||||
let r = ui.add_enabled(!eg.controls.is_empty(), egui::Button::new(tr("Export Template…")));
|
||||
app.auto.add("essential.export", r.rect, "Export Template");
|
||||
if r.clicked() {
|
||||
invoke.push(("essential.exportTemplate".into(), json!({"comp": cid.0})));
|
||||
@@ -234,7 +235,7 @@ fn controls(app: &mut EffectcraftApp, ui: &mut egui::Ui, cid: ItemId, list: &[Eg
|
||||
}
|
||||
}
|
||||
None => {
|
||||
ui.label(RichText::new("(missing)").color(t.danger));
|
||||
ui.label(RichText::new(tr("(missing)")).color(t.danger));
|
||||
}
|
||||
}
|
||||
link_menu(app, ui, cid, c, group, actions);
|
||||
@@ -243,10 +244,10 @@ fn controls(app: &mut EffectcraftApp, ui: &mut egui::Ui, cid: ItemId, list: &[Eg
|
||||
name_label(app, ui, cid, c, RichText::new(&c.name), actions);
|
||||
let item = essential::source_media(&app.session.project, cid, c);
|
||||
let name = item.and_then(|i| app.session.project.item(i)).map(|i| i.name.clone()).unwrap_or_default();
|
||||
ui.label(RichText::new(format!("▣ {name}")).color(t.text_dim));
|
||||
ui.label(RichText::new(tr_args("▣ {}", &[&name])).color(t.text_dim));
|
||||
}
|
||||
}
|
||||
let r = ui.add(egui::Button::new(RichText::new("×").size(10.0)).small().frame(false)).on_hover_text("Remove");
|
||||
let r = ui.add(egui::Button::new(RichText::new("×").size(10.0)).small().frame(false)).on_hover_text(tr("Remove"));
|
||||
app.auto.add(&format!("essential.control.{}.remove", c.id), r.rect, "Remove");
|
||||
if r.clicked() {
|
||||
actions.push(("essential.remove".into(), json!({"comp": cid.0, "control": c.id})));
|
||||
@@ -270,16 +271,16 @@ fn link_menu(app: &mut EffectcraftApp, ui: &mut egui::Ui, cid: ItemId, c: &EgCon
|
||||
};
|
||||
let has_sel = !app.session.state.selected_props.is_empty();
|
||||
let r = ui.menu_button(RichText::new("⋯").size(10.0), |ui| {
|
||||
if ui.button("Add Mirror").clicked() {
|
||||
if ui.button(tr("Add Mirror")).clicked() {
|
||||
actions.push(("essential.addMirror".into(), json!({"comp": cid.0, "control": c.id, "group": group})));
|
||||
ui.close();
|
||||
}
|
||||
if ui.add_enabled(has_sel, egui::Button::new("Link Selected Property")).clicked() {
|
||||
if ui.add_enabled(has_sel, egui::Button::new(tr("Link Selected Property"))).clicked() {
|
||||
actions.push(("essential.linkProperty".into(), json!({"comp": cid.0, "control": c.id})));
|
||||
ui.close();
|
||||
}
|
||||
for (layer, prop, name) in &links {
|
||||
if ui.button(format!("Unlink {name}")).clicked() {
|
||||
if ui.button(tr_args("Unlink {}", &[&name])).clicked() {
|
||||
actions.push(("essential.unlinkProperty".into(), json!({"comp": cid.0, "control": c.id, "layer": layer, "prop": prop})));
|
||||
ui.close();
|
||||
}
|
||||
@@ -460,7 +461,7 @@ fn instance(app: &mut EffectcraftApp, ui: &mut egui::Ui, actions: &mut Actions)
|
||||
let over = essential::overridden(&layer);
|
||||
let lt = layer.layer_time(app.session.time());
|
||||
ui.separator();
|
||||
ui.label(RichText::new(format!("Essential Properties — {}", layer.name)).strong());
|
||||
ui.label(RichText::new(tr_args("Essential Properties — {}", &[&layer.name])).strong());
|
||||
let group = essential::group(&layer).cloned().unwrap_or_else(|| effectcraft_engine::project::PropGroup::new(0, "", ""));
|
||||
for c in eg.flat() {
|
||||
let m = essential::match_id(c.id);
|
||||
@@ -527,12 +528,12 @@ fn instance(app: &mut EffectcraftApp, ui: &mut egui::Ui, actions: &mut Actions)
|
||||
}
|
||||
ui.horizontal(|ui| {
|
||||
let any = !over.is_empty();
|
||||
let r = ui.add_enabled(any, egui::Button::new("Push All to Comp"));
|
||||
let r = ui.add_enabled(any, egui::Button::new(tr("Push All to Comp")));
|
||||
app.auto.add("essential.instance.pushAll", r.rect, "Push Override Values to Source");
|
||||
if r.clicked() {
|
||||
actions.push(("essential.pushToComp".into(), json!({"layer": layer.id.0})));
|
||||
}
|
||||
let r = ui.add_enabled(any, egui::Button::new("Revert All"));
|
||||
let r = ui.add_enabled(any, egui::Button::new(tr("Revert All")));
|
||||
app.auto.add("essential.instance.revertAll", r.rect, "Revert All");
|
||||
if r.clicked() {
|
||||
actions.push(("essential.revert".into(), json!({"layer": layer.id.0})));
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
//! Every edit is a `prop.set` engine command (undoable; one drag = one undo step) and every
|
||||
//! handle registers an automation id.
|
||||
|
||||
use crate::i18n::{tr, tr_args};
|
||||
use effectcraft_engine::effects::{ColoramaPalette, OffsetCurve};
|
||||
use effectcraft_engine::geom::Mat3;
|
||||
use effectcraft_engine::keyframe::Value;
|
||||
@@ -110,7 +111,7 @@ fn tabs(
|
||||
fn reset_link(app: &mut EffectcraftApp, ui: &mut egui::Ui, p: &egui::Painter, r: Rect, id: &str) -> bool {
|
||||
let t = app.tokens;
|
||||
let resp = ui.interact(r, egui::Id::new(id), Sense::click());
|
||||
p.text(r.center(), Align2::CENTER_CENTER, "Reset", Tokens::ui(11.5), if resp.hovered() { t.hot_text } else { t.text_dim });
|
||||
p.text(r.center(), Align2::CENTER_CENTER, tr("Reset"), Tokens::ui(11.5), if resp.hovered() { t.hot_text } else { t.text_dim });
|
||||
app.auto.add(id, r, "Reset");
|
||||
resp.clicked()
|
||||
}
|
||||
@@ -196,7 +197,7 @@ fn rgb_curves(
|
||||
if let Some(np) = edit.changed {
|
||||
set_str(actions, layer, g, id, np.format(), Some(key));
|
||||
}
|
||||
p.text(pos2(gr.min.x, gr.max.y + 11.0), Align2::LEFT_CENTER, "Click adds a point; drag one off to remove", Tokens::ui(10.5), t.text_faint);
|
||||
p.text(pos2(gr.min.x, gr.max.y + 11.0), Align2::LEFT_CENTER, tr("Click adds a point; drag one off to remove"), Tokens::ui(10.5), t.text_faint);
|
||||
}
|
||||
|
||||
/// A hue / saturation curve's points (`x,y` in 0..1, y = ½ neutral), any count (empty = flat).
|
||||
@@ -394,7 +395,7 @@ fn huesat_curves(
|
||||
if reset_link(app, ui, p, Rect::from_min_size(pos2(gr.max.x - 44.0, gr.max.y + 4.0), vec2(44.0, 18.0)), &format!("{prefix}.reset")) {
|
||||
set_str(actions, layer, g, id, String::new(), None);
|
||||
}
|
||||
p.text(pos2(gr.min.x, gr.max.y + 13.0), Align2::LEFT_CENTER, "Click adds a point (½ height = no change)", Tokens::ui(10.5), t.text_faint);
|
||||
p.text(pos2(gr.min.x, gr.max.y + 13.0), Align2::LEFT_CENTER, tr("Click adds a point (½ height = no change)"), Tokens::ui(10.5), t.text_faint);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
@@ -458,7 +459,7 @@ fn colorama_wheel(
|
||||
let pal = if custom { ColoramaPalette::parse(&str_value(layer, ectx, g, "palette")) } else { None }.unwrap_or_else(|| ColoramaPalette::preset(preset));
|
||||
let x0 = r.min.x + 38.0;
|
||||
let label = if custom { "Custom palette" } else { effectcraft_engine::effects::COLORAMA_PRESETS.get(preset as usize).copied().unwrap_or("Preset") };
|
||||
p.text(pos2(x0, r.min.y + 15.0), Align2::LEFT_CENTER, format!("Output Cycle: {label}"), Tokens::ui(11.5), t.text_dim);
|
||||
p.text(pos2(x0, r.min.y + 15.0), Align2::LEFT_CENTER, tr_args("Output Cycle: {}", &[&label]), Tokens::ui(11.5), t.text_dim);
|
||||
let size = wheel_size(r.width());
|
||||
let c = pos2(x0 + size / 2.0, r.min.y + 30.0 + size / 2.0);
|
||||
let (outer, inner) = (size / 2.0 - 8.0, size / 2.0 - 26.0);
|
||||
@@ -572,7 +573,13 @@ fn colorama_wheel(
|
||||
widgets::open_popup(ui, pop);
|
||||
}
|
||||
app.auto.add(&format!("{prefix}.color"), sr, "Stop color");
|
||||
p.text(pos2(sr.max.x + 8.0, sr.center().y), Align2::LEFT_CENTER, format!("Stop {} at {:.0}°", sel + 1, ph * 360.0), Tokens::ui(11.0), t.text_dim);
|
||||
p.text(
|
||||
pos2(sr.max.x + 8.0, sr.center().y),
|
||||
Align2::LEFT_CENTER,
|
||||
tr_args("Stop {} at {}°", &[&(sel + 1), &format!("{:.0}", ph * 360.0)]),
|
||||
Tokens::ui(11.0),
|
||||
t.text_dim,
|
||||
);
|
||||
let mut rgb = [col[0], col[1], col[2]];
|
||||
if crate::header::color_popup(ui, pop, sr.left_bottom(), &mut rgb) {
|
||||
let mut st = pal.stops.clone();
|
||||
@@ -687,7 +694,7 @@ fn glow_map(app: &mut EffectcraftApp, ui: &mut egui::Ui, p: &egui::Painter, laye
|
||||
}
|
||||
p.rect_stroke(strip, 0.0, Stroke::new(1.0, t.field_border), StrokeKind::Outside);
|
||||
app.auto.add(&format!("{prefix}.colorMap"), strip, "Glow colour map");
|
||||
p.text(pos2(strip.max.x + 8.0, strip.center().y), Align2::LEFT_CENTER, "Colour map", Tokens::ui(10.5), t.text_faint);
|
||||
p.text(pos2(strip.max.x + 8.0, strip.center().y), Align2::LEFT_CENTER, tr("Colour map"), Tokens::ui(10.5), t.text_faint);
|
||||
if mode != 2 {
|
||||
return;
|
||||
}
|
||||
@@ -844,12 +851,12 @@ fn extractor_layers(
|
||||
None => "Custom".to_string(),
|
||||
};
|
||||
// Lined up with the parameter names and value popups below (Effect Controls' row layout).
|
||||
p.text(pos2(r.min.x + 37.0, r.center().y), Align2::LEFT_CENTER, "Layer", Tokens::ui(12.0), t.text);
|
||||
p.text(pos2(r.min.x + 37.0, r.center().y), Align2::LEFT_CENTER, tr("Layer"), Tokens::ui(12.0), t.text);
|
||||
let vx = (r.min.x + r.width() * 0.48).max(r.min.x + 161.0);
|
||||
let dr = Rect::from_min_size(pos2(vx, r.center().y - 9.0), vec2((r.max.x - vx - 56.0).clamp(80.0, 200.0), 18.0));
|
||||
let pop = egui::Id::new(("extractor-layers", euid));
|
||||
if widgets::dropdown(ui, dr, &label, &t, egui::Id::new(("extractor-layer", euid)))
|
||||
.on_hover_text("The OpenEXR layer shown in red, green, blue and alpha")
|
||||
.on_hover_text(tr("The OpenEXR layer shown in red, green, blue and alpha"))
|
||||
.clicked()
|
||||
{
|
||||
widgets::open_popup(ui, pop);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
//! tutorial (engine model in `effectcraft_engine::learn`). The coach highlights the step's target
|
||||
//! (an automation id) and moves on when the user performs the step's command; "Show me" runs it.
|
||||
|
||||
use crate::i18n::{tr, tr_args};
|
||||
use egui::{Align2, Color32, Rect, Sense, Stroke, StrokeKind, pos2, vec2};
|
||||
use serde_json::json;
|
||||
|
||||
@@ -13,7 +14,7 @@ pub fn home_tab(app: &mut EffectcraftApp, ui: &mut egui::Ui, area: Rect) {
|
||||
let t = app.tokens;
|
||||
let p = ui.painter().with_clip_rect(area);
|
||||
let (x, mut y, w) = (area.min.x, area.min.y, area.width());
|
||||
p.text(pos2(x, y + 6.0), Align2::LEFT_CENTER, "Learn", Tokens::semibold(16.0), Color32::WHITE);
|
||||
p.text(pos2(x, y + 6.0), Align2::LEFT_CENTER, tr("Learn"), Tokens::semibold(16.0), Color32::WHITE);
|
||||
p.text(
|
||||
pos2(x, y + 26.0),
|
||||
Align2::LEFT_CENTER,
|
||||
@@ -112,11 +113,11 @@ pub fn coach(app: &mut EffectcraftApp, ctx: &egui::Context) {
|
||||
.show(ui, |ui| {
|
||||
ui.set_width(w - 28.0);
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(egui::RichText::new("LEARN").font(Tokens::semibold(10.0)).color(t.accent));
|
||||
ui.label(egui::RichText::new(tr("LEARN")).font(Tokens::semibold(10.0)).color(t.accent));
|
||||
ui.label(egui::RichText::new(&tut.title).font(Tokens::ui(11.0)).color(t.text_dim));
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
let (cr, r) = ui.allocate_exact_size(vec2(16.0, 16.0), Sense::click());
|
||||
let r = r.on_hover_text("Close tutorial");
|
||||
let r = r.on_hover_text(tr("Close tutorial"));
|
||||
crate::icons::paint(ui.painter(), cr, crate::icons::Icon::Close, if r.hovered() { Color32::WHITE } else { t.text_dim });
|
||||
app.auto.add("learn.close", r.rect, "Close tutorial");
|
||||
if r.clicked() {
|
||||
@@ -140,37 +141,43 @@ pub fn coach(app: &mut EffectcraftApp, ctx: &egui::Context) {
|
||||
ui.add_space(4.0);
|
||||
match &step {
|
||||
Some(st) => {
|
||||
ui.label(egui::RichText::new(format!("Step {} of {n}: {}", i + 1, st.title)).font(Tokens::semibold(13.0)).color(Color32::WHITE));
|
||||
ui.label(
|
||||
egui::RichText::new(tr_args("Step {} of {}: {}", &[&(i + 1), &n, &st.title]))
|
||||
.font(Tokens::semibold(13.0))
|
||||
.color(Color32::WHITE),
|
||||
);
|
||||
ui.add_space(2.0);
|
||||
ui.label(egui::RichText::new(&st.text).font(Tokens::ui(12.0)).color(t.text));
|
||||
}
|
||||
None => {
|
||||
ui.label(egui::RichText::new("Tutorial complete").font(Tokens::semibold(13.0)).color(Color32::WHITE));
|
||||
ui.label(egui::RichText::new(tr("Tutorial complete")).font(Tokens::semibold(13.0)).color(Color32::WHITE));
|
||||
ui.label(
|
||||
egui::RichText::new("Nice work. Pick another tutorial on the Home screen's Learn tab.").font(Tokens::ui(12.0)).color(t.text),
|
||||
egui::RichText::new(tr("Nice work. Pick another tutorial on the Home screen's Learn tab."))
|
||||
.font(Tokens::ui(12.0))
|
||||
.color(t.text),
|
||||
);
|
||||
}
|
||||
}
|
||||
ui.add_space(8.0);
|
||||
ui.horizontal(|ui| {
|
||||
let back = ui.add_enabled(i > 0, egui::Button::new("Back"));
|
||||
let back = ui.add_enabled(i > 0, egui::Button::new(tr("Back")));
|
||||
app.auto.add("learn.back", back.rect, "Back");
|
||||
if back.clicked() {
|
||||
action = Some("back");
|
||||
}
|
||||
if step.is_some() {
|
||||
let show = ui.add(egui::Button::new("Show me"));
|
||||
let show = ui.add(egui::Button::new(tr("Show me")));
|
||||
app.auto.add("learn.showMe", show.rect, "Show me");
|
||||
if show.clicked() {
|
||||
action = Some("showMe");
|
||||
}
|
||||
let next = ui.add(egui::Button::new("Next"));
|
||||
let next = ui.add(egui::Button::new(tr("Next")));
|
||||
app.auto.add("learn.next", next.rect, "Next");
|
||||
if next.clicked() {
|
||||
action = Some("next");
|
||||
}
|
||||
} else {
|
||||
let more = ui.add(egui::Button::new("More tutorials"));
|
||||
let more = ui.add(egui::Button::new(tr("More tutorials")));
|
||||
app.auto.add("learn.more", more.rect, "More tutorials");
|
||||
if more.clicked() {
|
||||
action = Some("more");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Smaller panels: Preview, Audio, History, Markers and placeholders.
|
||||
|
||||
use crate::i18n::{tr, tr_args};
|
||||
use egui::{Align2, Color32, Rect, Sense, Stroke, pos2, vec2};
|
||||
use serde_json::json;
|
||||
|
||||
@@ -10,7 +11,7 @@ use crate::{EffectcraftApp, widgets};
|
||||
|
||||
pub fn placeholder(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect, p: PanelKind) {
|
||||
let t = app.tokens;
|
||||
ui.painter().text(rect.center(), Align2::CENTER_CENTER, format!("{} — coming soon", p.title()), Tokens::ui(12.0), t.text_faint);
|
||||
ui.painter().text(rect.center(), Align2::CENTER_CENTER, tr_args("{} — coming soon", &[&(p.title())]), Tokens::ui(12.0), t.text_faint);
|
||||
}
|
||||
|
||||
/// Preview panel: transport controls, the shortcut whose options are shown, and that
|
||||
@@ -158,7 +159,7 @@ pub fn preview(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
changes.push(json!({"shortcut": current.id(), "frameRate": v}));
|
||||
}
|
||||
let sx = xv + 106.0;
|
||||
p.text(pos2(sx, yy), Align2::LEFT_CENTER, "Skip", Tokens::ui(12.0), t.text_dim);
|
||||
p.text(pos2(sx, yy), Align2::LEFT_CENTER, tr("Skip"), Tokens::ui(12.0), t.text_dim);
|
||||
let (r, nv, _) = widgets::hot_number_at(ui, pos2(sx + 30.0, yy - 9.0), egui::Id::new("pv-skip"), o.skip as f64, 0.1, (0.0, 99.0), 0, "", &t);
|
||||
app.auto.add("preview.skip", r, "Skip");
|
||||
if let Some(nv) = nv {
|
||||
@@ -194,7 +195,7 @@ pub fn preview(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
yy += 24.0;
|
||||
|
||||
// What stopping does.
|
||||
p.text(pos2(x0, yy), Align2::LEFT_CENTER, format!("On ({}) stop:", current.label()), Tokens::ui(11.5), t.text_dim);
|
||||
p.text(pos2(x0, yy), Align2::LEFT_CENTER, tr_args("On ({}) stop:", &[&(current.label())]), Tokens::ui(11.5), t.text_dim);
|
||||
yy += 20.0;
|
||||
if check_row(app, ui, &p, pos2(x0 - 2.0, yy), "playCachedFrames", "If caching, play cached frames", o.play_cached_frames) {
|
||||
changes.push(json!({"shortcut": current.id(), "playCachedFrames": !o.play_cached_frames}));
|
||||
@@ -353,7 +354,7 @@ pub fn audio(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
}
|
||||
}
|
||||
None => {
|
||||
p.text(pos2(lx, top + 12.0), Align2::LEFT_CENTER, "No audio layer selected", Tokens::ui(11.0), t.text_faint);
|
||||
p.text(pos2(lx, top + 12.0), Align2::LEFT_CENTER, tr("No audio layer selected"), Tokens::ui(11.0), t.text_faint);
|
||||
}
|
||||
}
|
||||
if app.meter.active() || app.audio.is_some() {
|
||||
@@ -425,7 +426,7 @@ pub fn markers(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
let scroll = widgets::PanelScroll::begin(ui, egui::Id::new("markers-scroll"), rect);
|
||||
let mut y = rect.min.y + 8.0 - scroll.offset;
|
||||
if c.markers.is_empty() {
|
||||
p.text(rect.center(), Align2::CENTER_CENTER, "No composition markers", Tokens::ui(12.0), t.text_faint);
|
||||
p.text(rect.center(), Align2::CENTER_CENTER, tr("No composition markers"), Tokens::ui(12.0), t.text_faint);
|
||||
}
|
||||
for m in &c.markers {
|
||||
let r = Rect::from_min_size(pos2(rect.min.x, y), vec2(rect.width(), 22.0));
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
//! composition duration, Open New Composition), the Composition Navigator bar above the viewer
|
||||
//! (the flow of nested comps, click to open) and the Composition Mini-Flowchart popup (Tab).
|
||||
|
||||
use crate::i18n::{tr, tr_args};
|
||||
use effectcraft_engine::project::{ItemId, LayerSource, Project};
|
||||
use egui::{Align2, Color32, Rect, Sense, Stroke, pos2, vec2};
|
||||
use serde_json::json;
|
||||
@@ -112,13 +113,13 @@ pub fn dialog(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
|
||||
let (mut ok, mut cancel) = (false, false);
|
||||
super::dialogs::modal(ctx, "Pre-compose", vec2(520.0, 360.0), t, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("New composition name:");
|
||||
ui.label(tr("New composition name:"));
|
||||
let r = ui.add(egui::TextEdit::singleline(&mut d.name).desired_width(260.0));
|
||||
app.auto.add("dialog.precompose.name", r.rect, "New composition name");
|
||||
});
|
||||
ui.add_space(10.0);
|
||||
ui.add_enabled_ui(d.can_leave, |ui| {
|
||||
let r = ui.radio_value(&mut d.leave, true, format!("Leave all attributes in \"{}\"", d.layer_name));
|
||||
let r = ui.radio_value(&mut d.leave, true, tr_args("Leave all attributes in \"{}\"", &[&d.layer_name]));
|
||||
app.auto.add("dialog.precompose.leave", r.rect, "Leave all attributes");
|
||||
ui.label(
|
||||
egui::RichText::new(format!(
|
||||
@@ -133,24 +134,24 @@ pub fn dialog(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
|
||||
let r = ui.radio_value(&mut d.leave, false, "Move all attributes into the new composition");
|
||||
app.auto.add("dialog.precompose.move", r.rect, "Move all attributes");
|
||||
ui.label(
|
||||
egui::RichText::new("Use this option to place the currently selected layers together into a new intermediate composition.")
|
||||
egui::RichText::new(tr("Use this option to place the currently selected layers together into a new intermediate composition."))
|
||||
.color(t.text_dim)
|
||||
.size(11.0),
|
||||
);
|
||||
ui.add_space(4.0);
|
||||
ui.add_enabled_ui(!d.leave, |ui| {
|
||||
let r = ui.checkbox(&mut d.adjust, "Adjust composition duration to the time span of the selected layers");
|
||||
let r = ui.checkbox(&mut d.adjust, tr("Adjust composition duration to the time span of the selected layers"));
|
||||
app.auto.add("dialog.precompose.adjust", r.rect, "Adjust composition duration");
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
let r = ui.checkbox(&mut d.open, "Open New Composition");
|
||||
let r = ui.checkbox(&mut d.open, tr("Open New Composition"));
|
||||
app.auto.add("dialog.precompose.open", r.rect, "Open New Composition");
|
||||
ui.add_space(14.0);
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
let r = ui.add(egui::Button::new(egui::RichText::new(" OK ").color(Color32::WHITE)).fill(t.accent));
|
||||
let r = ui.add(egui::Button::new(egui::RichText::new(tr(" OK ")).color(Color32::WHITE)).fill(t.accent));
|
||||
app.auto.add("dialog.precompose.ok", r.rect, "OK");
|
||||
ok = r.clicked();
|
||||
let r = ui.button("Cancel");
|
||||
let r = ui.button(tr("Cancel"));
|
||||
app.auto.add("dialog.precompose.cancel", r.rect, "Cancel");
|
||||
cancel = r.clicked();
|
||||
});
|
||||
@@ -222,7 +223,7 @@ pub fn navigator(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
}
|
||||
let fr = Rect::from_min_size(pos2(rect.max.x - 26.0, rect.min.y + 2.0), vec2(22.0, rect.height() - 4.0));
|
||||
if crate::widgets::icon_button(ui, fr, crate::icons::Icon::Flowchart, false, &t, egui::Id::new("comp-nav-flow"))
|
||||
.on_hover_text("Composition Mini-Flowchart")
|
||||
.on_hover_text(tr("Composition Mini-Flowchart"))
|
||||
.clicked()
|
||||
{
|
||||
app.ui.mini_flowchart = Some([fr.min.x, fr.max.y]);
|
||||
@@ -300,7 +301,7 @@ pub fn mini_flowchart(app: &mut EffectcraftApp, ctx: &egui::Context) {
|
||||
}
|
||||
}
|
||||
if ups.is_empty() && downs.is_empty() {
|
||||
p.text(pos2(rect.center().x, rect.max.y - 4.0), Align2::CENTER_BOTTOM, "No nested compositions", Tokens::ui(10.5), t.text_faint);
|
||||
p.text(pos2(rect.center().x, rect.max.y - 4.0), Align2::CENTER_BOTTOM, tr("No nested compositions"), Tokens::ui(10.5), t.text_faint);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
//! every setting; entries needing numbers (custom time span and frame rate, crop, resize, bitrate,
|
||||
//! key frame interval, Make Template) open a form that runs the same command.
|
||||
|
||||
use crate::i18n::{tr, tr_args};
|
||||
use effectcraft_engine::project::render_queue::{
|
||||
AlphaMode, AudioFormat, AudioOutput, Channels, ColorDepth, CurrentOrOff, EffectsMode, FieldRender, OpusApplication, OutputFormat, PostRenderAction,
|
||||
ProResProfile, Pulldown, RESIZE_PRESETS, RenderLog, RenderQuality, RenderQueueItem, RenderStatus, ResizeQuality, SwitchOverride, TimeSpan, WebmVideoCodec,
|
||||
@@ -78,33 +79,33 @@ fn settings_menu(it: &RenderQueueItem, templates: &RenderTemplates) -> Vec<(Stri
|
||||
}
|
||||
v.push(("-".into(), Value::Null));
|
||||
for (u, k) in effectcraft_engine::project::render_queue::ProxyUse::ALL.iter().zip(["current", "all", "comp", "none"]) {
|
||||
v.push((mark(s.proxy_use == *u, &format!("Proxy Use: {}", u.label())), json!({"proxyUse": k})));
|
||||
v.push((mark(s.proxy_use == *u, &tr_args("Proxy Use: {}", &[&(u.label())])), json!({"proxyUse": k})));
|
||||
}
|
||||
for (e, k) in EffectsMode::ALL.iter().zip(["current", "allOn", "allOff"]) {
|
||||
v.push((mark(s.effects == *e, &format!("Effects: {}", e.label())), json!({"effects": k})));
|
||||
v.push((mark(s.effects == *e, &tr_args("Effects: {}", &[&(e.label())])), json!({"effects": k})));
|
||||
}
|
||||
for (e, k) in [(CurrentOrOff::Current, "current"), (CurrentOrOff::AllOff, "allOff")] {
|
||||
v.push((mark(s.solo == e, &format!("Solo Switches: {}", e.label())), json!({"solo": k})));
|
||||
v.push((mark(s.solo == e, &tr_args("Solo Switches: {}", &[&(e.label())])), json!({"solo": k})));
|
||||
}
|
||||
for (e, k) in [(CurrentOrOff::Current, "current"), (CurrentOrOff::AllOff, "allOff")] {
|
||||
v.push((mark(s.guide_layers == e, &format!("Guide Layers: {}", e.label())), json!({"guideLayers": k})));
|
||||
v.push((mark(s.guide_layers == e, &tr_args("Guide Layers: {}", &[&(e.label())])), json!({"guideLayers": k})));
|
||||
}
|
||||
for (d, k) in ColorDepth::ALL.iter().zip(["current", "8", "16", "32"]) {
|
||||
v.push((mark(s.color_depth == *d, &format!("Color Depth: {}", d.label())), json!({"colorDepth": k})));
|
||||
v.push((mark(s.color_depth == *d, &tr_args("Color Depth: {}", &[&(d.label())])), json!({"colorDepth": k})));
|
||||
}
|
||||
v.push(("-".into(), Value::Null));
|
||||
for (m, k) in SwitchOverride::ALL.iter().zip(["current", "onForChecked", "offForAll"]) {
|
||||
v.push((mark(s.frame_blending == *m, &format!("Frame Blending: {}", m.label())), json!({"frameBlending": k})));
|
||||
v.push((mark(s.frame_blending == *m, &tr_args("Frame Blending: {}", &[&(m.label())])), json!({"frameBlending": k})));
|
||||
}
|
||||
for (m, k) in SwitchOverride::ALL.iter().zip(["current", "onForChecked", "offForAll"]) {
|
||||
v.push((mark(s.motion_blur_override() == *m, &format!("Motion Blur: {}", m.label())), json!({"motionBlur": k})));
|
||||
v.push((mark(s.motion_blur_override() == *m, &tr_args("Motion Blur: {}", &[&(m.label())])), json!({"motionBlur": k})));
|
||||
}
|
||||
for (f, k) in FieldRender::ALL.iter().zip(["off", "upper", "lower"]) {
|
||||
v.push((mark(s.field_render == *f, &format!("Field Render: {}", f.label())), json!({"fieldRender": k})));
|
||||
v.push((mark(s.field_render == *f, &tr_args("Field Render: {}", &[&(f.label())])), json!({"fieldRender": k})));
|
||||
}
|
||||
if s.field_render != FieldRender::Off {
|
||||
for pd in Pulldown::ALL {
|
||||
v.push((mark(s.pulldown == pd, &format!("3:2 Pulldown: {}", pd.label())), json!({"pulldown": pd.label()})));
|
||||
v.push((mark(s.pulldown == pd, &tr_args("3:2 Pulldown: {}", &[&(pd.label())])), json!({"pulldown": pd.label()})));
|
||||
}
|
||||
}
|
||||
v.push(("-".into(), Value::Null));
|
||||
@@ -136,7 +137,7 @@ fn output_menu(it: &RenderQueueItem, available: &[OutputFormat], templates: &Ren
|
||||
v.push(("-".into(), Value::Null));
|
||||
for f in OutputFormat::ALL {
|
||||
if available.contains(&f) {
|
||||
v.push((mark(o.format == f, &format!("Format: {}", f.label())), json!({"format": format!("{f:?}")})));
|
||||
v.push((mark(o.format == f, &tr_args("Format: {}", &[&(f.label())])), json!({"format": format!("{f:?}")})));
|
||||
}
|
||||
}
|
||||
v.push(("-".into(), Value::Null));
|
||||
@@ -168,7 +169,7 @@ fn output_menu(it: &RenderQueueItem, available: &[OutputFormat], templates: &Ren
|
||||
OutputFormat::WebM => {
|
||||
v.push(("-".into(), Value::Null));
|
||||
for c in [WebmVideoCodec::Vp9, WebmVideoCodec::Av1] {
|
||||
v.push((mark(o.webm_codec == c, &format!("Video Codec: {}", c.label())), json!({"webmCodec": c.label().to_ascii_lowercase()})));
|
||||
v.push((mark(o.webm_codec == c, &tr_args("Video Codec: {}", &[&(c.label())])), json!({"webmCodec": c.label().to_ascii_lowercase()})));
|
||||
}
|
||||
if o.webm_codec == WebmVideoCodec::Av1 {
|
||||
codec_menu(o, &mut v);
|
||||
@@ -185,7 +186,7 @@ fn output_menu(it: &RenderQueueItem, available: &[OutputFormat], templates: &Ren
|
||||
v.push((mark(o.opus_bitrate_kbps == kbps, &format!("Opus Bitrate: {kbps} kbps")), json!({"audioBitrate": kbps})));
|
||||
}
|
||||
for (a, k) in [(OpusApplication::Audio, "audio"), (OpusApplication::Voip, "voice")] {
|
||||
v.push((mark(o.opus_application == a, &format!("Opus Tuning: {}", a.label())), json!({"opusApplication": k})));
|
||||
v.push((mark(o.opus_application == a, &tr_args("Opus Tuning: {}", &[&(a.label())])), json!({"opusApplication": k})));
|
||||
}
|
||||
}
|
||||
OutputFormat::JpegSequence => {
|
||||
@@ -226,7 +227,7 @@ fn output_menu(it: &RenderQueueItem, available: &[OutputFormat], templates: &Ren
|
||||
(PostRenderAction::ImportAndReplace, "importAndReplace"),
|
||||
(PostRenderAction::SetProxy, "setProxy"),
|
||||
] {
|
||||
v.push((mark(it.post_render == a, &format!("Post-Render Action: {}", a.label())), json!({"postRenderAction": k})));
|
||||
v.push((mark(it.post_render == a, &tr_args("Post-Render Action: {}", &[&(a.label())])), json!({"postRenderAction": k})));
|
||||
}
|
||||
v.push((mark(o.include_project_link, "Include Project Link"), json!({"includeProjectLink": !o.include_project_link})));
|
||||
if o.format.supports_audio() {
|
||||
@@ -246,7 +247,7 @@ fn output_menu(it: &RenderQueueItem, available: &[OutputFormat], templates: &Ren
|
||||
v.push((mark(o.audio_channels != 1, "Audio Channels: Stereo"), json!({"audioChannels": "stereo"})));
|
||||
if matches!(o.format, OutputFormat::ProRes | OutputFormat::Wav | OutputFormat::Aiff) {
|
||||
for f in [AudioFormat::S16, AudioFormat::S24, AudioFormat::F32] {
|
||||
v.push((mark(o.audio_format == f, &format!("Sample Format: {}", f.label())), json!({"audioFormat": f.label()})));
|
||||
v.push((mark(o.audio_format == f, &tr_args("Sample Format: {}", &[&(f.label())])), json!({"audioFormat": f.label()})));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -259,7 +260,7 @@ fn codec_menu(o: &effectcraft_engine::project::render_queue::OutputModule, v: &m
|
||||
let c = &o.codec;
|
||||
v.push(("-".into(), Value::Null));
|
||||
for p in CodecProfile::ALL {
|
||||
v.push((mark(c.profile == p, &format!("Profile: {}", p.label())), json!({"profile": format!("{p:?}").to_ascii_lowercase()})));
|
||||
v.push((mark(c.profile == p, &tr_args("Profile: {}", &[&(p.label())])), json!({"profile": format!("{p:?}").to_ascii_lowercase()})));
|
||||
}
|
||||
v.push(("-".into(), Value::Null));
|
||||
v.push((mark(c.level.is_none(), "Level: Auto"), json!({"level": "auto"})));
|
||||
@@ -271,7 +272,7 @@ fn codec_menu(o: &effectcraft_engine::project::render_queue::OutputModule, v: &m
|
||||
}
|
||||
v.push(("-".into(), Value::Null));
|
||||
for r in [RateControlMode::Bitrate, RateControlMode::Quality] {
|
||||
v.push((mark(c.rate_control == r, &format!("Rate Control: {}", r.label())), json!({"rateControl": format!("{r:?}").to_ascii_lowercase()})));
|
||||
v.push((mark(c.rate_control == r, &tr_args("Rate Control: {}", &[&(r.label())])), json!({"rateControl": format!("{r:?}").to_ascii_lowercase()})));
|
||||
}
|
||||
if c.rate_control == RateControlMode::Bitrate {
|
||||
for kbps in [1_000u32, 2_000, 5_000, 10_000, 20_000, 40_000] {
|
||||
@@ -438,7 +439,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
let top = Rect::from_min_size(rect.min, vec2(rect.width(), TOP_H));
|
||||
p.rect_filled(top, 0.0, t.tl_ruler_bg);
|
||||
let x0 = rect.min.x + 12.0;
|
||||
p.text(pos2(x0, top.min.y + 16.0), Align2::LEFT_CENTER, "Current Render", Tokens::semibold(12.5), t.text);
|
||||
p.text(pos2(x0, top.min.y + 16.0), Align2::LEFT_CENTER, tr("Current Render"), Tokens::semibold(12.5), t.text);
|
||||
let cur_item = progress.as_ref().and_then(|p| p.current).and_then(|id| queue.iter().find(|i| i.id == id));
|
||||
let cur_name = cur_item.and_then(|i| app.session.project.item(i.comp)).map(|i| i.name.clone());
|
||||
if let Some(pr) = &progress {
|
||||
@@ -465,7 +466,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
if widgets::checkbox(ui, nb, notify, &t, egui::Id::new("rq-notify")).clicked() {
|
||||
actions.push(("renderQueue.setNotify", json!({"notify": !notify})));
|
||||
}
|
||||
p.text(pos2(nb.max.x + 5.0, nb.center().y), Align2::LEFT_CENTER, "Notify", Tokens::ui(11.5), t.text_dim);
|
||||
p.text(pos2(nb.max.x + 5.0, nb.center().y), Align2::LEFT_CENTER, tr("Notify"), Tokens::ui(11.5), t.text_dim);
|
||||
app.auto.add("renderQueue.notify", nb, if notify { "Notify: on" } else { "Notify: off" });
|
||||
if rendering {
|
||||
if widgets::text_button(ui, stop_b, "Stop", false, &t, egui::Id::new("rq-stop")).clicked() {
|
||||
@@ -531,7 +532,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
ui.set_clip_rect(list.intersect(panel_clip));
|
||||
let mut y = list.min.y - scroll.offset;
|
||||
if queue.is_empty() {
|
||||
lp.text(pos2(list.center().x, list.min.y + 30.0), Align2::CENTER_CENTER, "The render queue is empty.", Tokens::ui(12.0), t.text_faint);
|
||||
lp.text(pos2(list.center().x, list.min.y + 30.0), Align2::CENTER_CENTER, tr("The render queue is empty."), Tokens::ui(12.0), t.text_faint);
|
||||
}
|
||||
let editing_id = egui::Id::new("rq-edit-output");
|
||||
let mut editing: Option<(u64, String)> = ui.data(|d| d.get_temp(editing_id));
|
||||
@@ -559,19 +560,19 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
}
|
||||
let item_id = it.id;
|
||||
row_resp.context_menu(|ui| {
|
||||
if ui.button("Duplicate").clicked() {
|
||||
if ui.button(tr("Duplicate")).clicked() {
|
||||
actions.push(("renderQueue.duplicate", json!({"item": item_id})));
|
||||
ui.close();
|
||||
}
|
||||
if ui.button("Remove").clicked() {
|
||||
if ui.button(tr("Remove")).clicked() {
|
||||
actions.push(("renderQueue.remove", json!({"item": item_id})));
|
||||
ui.close();
|
||||
}
|
||||
if k > 0 && ui.button("Move Up").clicked() {
|
||||
if k > 0 && ui.button(tr("Move Up")).clicked() {
|
||||
actions.push(("renderQueue.move", json!({"item": item_id, "to": n - 1})));
|
||||
ui.close();
|
||||
}
|
||||
if n < queue.len() && ui.button("Move Down").clicked() {
|
||||
if n < queue.len() && ui.button(tr("Move Down")).clicked() {
|
||||
actions.push(("renderQueue.move", json!({"item": item_id, "to": n + 1})));
|
||||
ui.close();
|
||||
}
|
||||
@@ -670,7 +671,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
}
|
||||
// Log: Errors Only / Plus Settings / Plus Per Frame Info.
|
||||
let ld = Rect::from_min_size(pos2(r.max.x - 190.0, r.min.y + 3.0), vec2(150.0, ROW_H - 6.0));
|
||||
lp.text(pos2(ld.min.x - 6.0, r.center().y), Align2::RIGHT_CENTER, "Log:", Tokens::ui(11.5), t.text_dim);
|
||||
lp.text(pos2(ld.min.x - 6.0, r.center().y), Align2::RIGHT_CENTER, tr("Log:"), Tokens::ui(11.5), t.text_dim);
|
||||
let lid = egui::Id::new(("rq-log", it.id));
|
||||
if widgets::dropdown(ui, ld, it.log.label(), &t, lid).clicked() && !rendering {
|
||||
widgets::open_popup(ui, lid);
|
||||
@@ -684,7 +685,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
}
|
||||
// Output To.
|
||||
let ox = dd.max.x + 14.0;
|
||||
lp.text(pos2(ox, r.center().y), Align2::LEFT_CENTER, "Output To:", Tokens::ui(11.5), t.text_dim);
|
||||
lp.text(pos2(ox, r.center().y), Align2::LEFT_CENTER, tr("Output To:"), Tokens::ui(11.5), t.text_dim);
|
||||
let odd = Rect::from_min_size(pos2(ox + 62.0, r.min.y + 3.0), vec2(22.0, ROW_H - 6.0));
|
||||
let oid = egui::Id::new(("rq-out", it.id));
|
||||
if widgets::dropdown(ui, odd, "", &t, oid).clicked() && !rendering {
|
||||
@@ -737,7 +738,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
}
|
||||
}
|
||||
let how = if dialog { "Click to choose where to save; Alt-click to edit the template" } else { "Click to edit" };
|
||||
resp.on_hover_text(format!("Template: {}\n{how}", it.output.output));
|
||||
resp.on_hover_text(tr_args("Template: {}\n{}", &[&it.output.output, &how]));
|
||||
app.auto.add(&aid("outputPath"), path_rect, &shown);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
//! `templates.default.<Slot>`, `templates.select`, `templates.duplicate`, `templates.delete`,
|
||||
//! `templates.newName`, `templates.saveFromItem`, `templates.ok`.
|
||||
|
||||
use crate::i18n::{tr, tr_args};
|
||||
use effectcraft_engine::project::render_templates::{RenderTemplates, TemplateKind, TemplateSlot};
|
||||
use egui::{Color32, vec2};
|
||||
use serde_json::{Value, json};
|
||||
@@ -51,7 +52,7 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
|
||||
let mut close = false;
|
||||
let title = if st.output { "Output Module Templates" } else { "Render Settings Templates" };
|
||||
super::dialogs::modal(ctx, title, vec2(600.0, 560.0), t, |ui| {
|
||||
ui.label(egui::RichText::new("Defaults").font(Tokens::semibold(12.5)).color(t.text));
|
||||
ui.label(egui::RichText::new(tr("Defaults")).font(Tokens::semibold(12.5)).color(t.text));
|
||||
egui::Grid::new("tpl-defaults").num_columns(2).spacing(vec2(14.0, 6.0)).show(ui, |ui| {
|
||||
for slot in TemplateSlot::ALL {
|
||||
ui.label(slot.label());
|
||||
@@ -71,9 +72,9 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
|
||||
});
|
||||
ui.add_space(12.0);
|
||||
ui.separator();
|
||||
ui.label(egui::RichText::new("Settings").font(Tokens::semibold(12.5)).color(t.text));
|
||||
ui.label(egui::RichText::new(tr("Settings")).font(Tokens::semibold(12.5)).color(t.text));
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Settings Name:");
|
||||
ui.label(tr("Settings Name:"));
|
||||
let r = egui::ComboBox::from_id_salt("tpl-select").selected_text(&st.selected).width(300.0).show_ui(ui, |ui| {
|
||||
for n in &list {
|
||||
ui.selectable_value(&mut st.selected, n.clone(), n);
|
||||
@@ -106,7 +107,7 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
|
||||
});
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
let r = ui.button("Duplicate");
|
||||
let r = ui.button(tr("Duplicate"));
|
||||
app.auto.add("templates.duplicate", r.rect, "Duplicate");
|
||||
if r.clicked() {
|
||||
let mut n = format!("{} copy", st.selected);
|
||||
@@ -118,13 +119,13 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
|
||||
actions.push(("renderQueue.saveTemplate", json!({"kind": kind, "name": n, "from": st.selected})));
|
||||
st.selected = n;
|
||||
}
|
||||
let r = ui.add_enabled(!builtin, egui::Button::new("Delete"));
|
||||
let r = ui.add_enabled(!builtin, egui::Button::new(tr("Delete")));
|
||||
app.auto.add("templates.delete", r.rect, "Delete");
|
||||
if r.clicked() {
|
||||
actions.push(("renderQueue.deleteTemplate", json!({"kind": kind, "name": st.selected})));
|
||||
}
|
||||
if builtin {
|
||||
ui.label(egui::RichText::new("built-in").color(t.text_faint).size(11.0));
|
||||
ui.label(egui::RichText::new(tr("built-in")).color(t.text_faint).size(11.0));
|
||||
}
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
@@ -132,12 +133,12 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
|
||||
// New template from the selected (or only) Render Queue item.
|
||||
let queue = &app.session.project.render_queue;
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("New from Render Queue item:");
|
||||
let r = ui.add(egui::TextEdit::singleline(&mut st.new_name).hint_text("template name").desired_width(180.0));
|
||||
ui.label(tr("New from Render Queue item:"));
|
||||
let r = ui.add(egui::TextEdit::singleline(&mut st.new_name).hint_text(tr("template name")).desired_width(180.0));
|
||||
app.auto.add("templates.newName", r.rect, &st.new_name);
|
||||
let item = queue.last().map(|i| i.id);
|
||||
let ok = item.is_some() && !st.new_name.trim().is_empty();
|
||||
let r = ui.add_enabled(ok, egui::Button::new(format!("Save from #{}", queue.len().max(1))));
|
||||
let r = ui.add_enabled(ok, egui::Button::new(tr_args("Save from #{}", &[&(queue.len().max(1))])));
|
||||
app.auto.add("templates.saveFromItem", r.rect, "Save from item");
|
||||
if r.clicked()
|
||||
&& let Some(id) = item
|
||||
@@ -149,7 +150,7 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
|
||||
});
|
||||
ui.add_space(12.0);
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
let r = ui.add(egui::Button::new(egui::RichText::new(" OK ").color(Color32::WHITE)).fill(t.accent));
|
||||
let r = ui.add(egui::Button::new(egui::RichText::new(tr(" OK ")).color(Color32::WHITE)).fill(t.accent));
|
||||
app.auto.add("templates.ok", r.rect, "OK");
|
||||
close = r.clicked();
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
//! time, with the colour standard (Rec. 601 / 709 / 2020), 8-bit or float scale and clamp. The
|
||||
//! maths is `effectcraft_raster::scopes`; agents read the same numbers with `scopes.analyze`.
|
||||
|
||||
use crate::i18n::tr;
|
||||
use effectcraft_engine::commands::panels_cmds::scope_frame;
|
||||
use effectcraft_engine::raster::scopes::{self, ColorStandard, Scope, ScopeKind, ScopeOpts};
|
||||
use egui::{Align2, Color32, Rect, Stroke, pos2, vec2};
|
||||
@@ -75,7 +76,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
p.rect_filled(area, 2.0, Color32::from_rgb(0x10, 0x10, 0x10));
|
||||
app.auto.add("scopes.plot", area, kind.label());
|
||||
let Some(cid) = app.session.active_comp_id() else {
|
||||
p.text(area.center(), Align2::CENTER_CENTER, "Open a composition to see its scopes", Tokens::ui(12.0), t.text_faint);
|
||||
p.text(area.center(), Align2::CENTER_CENTER, tr("Open a composition to see its scopes"), Tokens::ui(12.0), t.text_faint);
|
||||
return;
|
||||
};
|
||||
// Plot rectangle: square for vectorscopes.
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
//! `shortcuts.rename`, `shortcuts.delete`, `shortcuts.import`, `shortcuts.export`,
|
||||
//! `shortcuts.resetPreset`, `shortcuts.close`.
|
||||
|
||||
use crate::i18n::{tr, tr_args};
|
||||
use effectcraft_engine::shortcuts::{APP_SCOPE, Bindable, DEFAULT_PRESET, normalize};
|
||||
use egui::{Align2, Color32, Rect, RichText, Sense, Stroke, StrokeKind, pos2, vec2};
|
||||
use serde_json::{Value, json};
|
||||
@@ -237,7 +238,7 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
|
||||
super::dialogs::modal(ctx, "Keyboard Shortcuts", vec2(1000.0, 720.0), t, |ui| {
|
||||
// ---- preset bar
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Preset:");
|
||||
ui.label(tr("Preset:"));
|
||||
let r = egui::ComboBox::from_id_salt("sc-preset").selected_text(&active).width(220.0).show_ui(ui, |ui| {
|
||||
for n in &presets {
|
||||
if ui.selectable_label(*n == active, n).clicked() {
|
||||
@@ -247,7 +248,7 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
|
||||
});
|
||||
app.auto.add("shortcuts.preset", r.response.rect, "Preset");
|
||||
let custom = active != DEFAULT_PRESET;
|
||||
let b = ui.button("Duplicate");
|
||||
let b = ui.button(tr("Duplicate"));
|
||||
app.auto.add("shortcuts.duplicate", b.rect, "Duplicate");
|
||||
if b.clicked() {
|
||||
run.push(("shortcuts.preset".into(), json!({"op": "duplicate", "from": active, "name": format!("{active} copy")})));
|
||||
@@ -255,35 +256,35 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
|
||||
if let Some(name) = &mut st.rename {
|
||||
let r = ui.add(egui::TextEdit::singleline(name).desired_width(140.0));
|
||||
app.auto.add("shortcuts.renameField", r.rect, "New name");
|
||||
if ui.button("Rename").clicked() || (r.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter))) {
|
||||
if ui.button(tr("Rename")).clicked() || (r.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter))) {
|
||||
run.push(("shortcuts.preset".into(), json!({"op": "rename", "name": active, "newName": name})));
|
||||
st.rename = None;
|
||||
}
|
||||
} else {
|
||||
let b = ui.add_enabled(custom, egui::Button::new("Rename"));
|
||||
let b = ui.add_enabled(custom, egui::Button::new(tr("Rename")));
|
||||
app.auto.add("shortcuts.rename", b.rect, "Rename");
|
||||
if b.clicked() {
|
||||
st.rename = Some(active.clone());
|
||||
}
|
||||
}
|
||||
let b = ui.add_enabled(custom, egui::Button::new("Delete"));
|
||||
let b = ui.add_enabled(custom, egui::Button::new(tr("Delete")));
|
||||
app.auto.add("shortcuts.delete", b.rect, "Delete");
|
||||
if b.clicked() {
|
||||
run.push(("shortcuts.preset".into(), json!({"op": "delete", "name": active})));
|
||||
}
|
||||
let b = ui.add_enabled(custom, egui::Button::new("Reset Preset"));
|
||||
let b = ui.add_enabled(custom, egui::Button::new(tr("Reset Preset")));
|
||||
app.auto.add("shortcuts.resetPreset", b.rect, "Reset Preset");
|
||||
if b.clicked() {
|
||||
run.push(("shortcuts.reset".into(), json!({})));
|
||||
}
|
||||
let b = ui.button("Import...");
|
||||
let b = ui.button(tr("Import..."));
|
||||
app.auto.add("shortcuts.import", b.rect, "Import");
|
||||
if b.clicked()
|
||||
&& let Some(p) = app.hooks.pick_files.as_ref().and_then(|f| f(&["json"]).into_iter().next())
|
||||
{
|
||||
run.push(("shortcuts.import".into(), json!({"path": p})));
|
||||
}
|
||||
let b = ui.button("Export...");
|
||||
let b = ui.button(tr("Export..."));
|
||||
app.auto.add("shortcuts.export", b.rect, "Export");
|
||||
if b.clicked()
|
||||
&& let Some(p) = app.hooks.pick_save_file.as_ref().and_then(|f| f(&format!("{active}.json"), "json"))
|
||||
@@ -294,7 +295,7 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
|
||||
ui.add_space(6.0);
|
||||
// ---- modifiers + legend
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Modifiers:");
|
||||
ui.label(tr("Modifiers:"));
|
||||
let names = if cfg!(target_os = "macos") { ["⌃ Ctrl", "⌘ Cmd", "⌥ Opt", "⇧ Shift"] } else { ["", "Ctrl", "Alt", "Shift"] };
|
||||
for (i, (n, id)) in names.iter().zip(["ctrl", "cmd", "alt", "shift"]).enumerate() {
|
||||
if n.is_empty() {
|
||||
@@ -396,13 +397,13 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
|
||||
p.rect_filled(info, 4.0, t.row);
|
||||
let p = ui.painter_at(info.shrink(2.0));
|
||||
if let Some(k) = &st.key {
|
||||
p.text(info.left_top() + vec2(8.0, 6.0), Align2::LEFT_TOP, format!("Key: {k}"), Tokens::semibold(12.0), t.tab_text_active);
|
||||
p.text(info.left_top() + vec2(8.0, 6.0), Align2::LEFT_TOP, tr_args("Key: {}", &[&k]), Tokens::semibold(12.0), t.tab_text_active);
|
||||
let mut yy = info.min.y + 26.0;
|
||||
let mut list: Vec<(String, String, String)> =
|
||||
table.bindings().into_iter().filter(|(sc, _)| split(sc).1 == *k).map(|(sc, b)| (sc.to_string(), b.label.clone(), b.scope.clone())).collect();
|
||||
list.sort();
|
||||
if list.is_empty() {
|
||||
p.text(pos2(info.min.x + 8.0, yy), Align2::LEFT_TOP, "Not assigned", Tokens::ui(11.5), t.text_dim);
|
||||
p.text(pos2(info.min.x + 8.0, yy), Align2::LEFT_TOP, tr("Not assigned"), Tokens::ui(11.5), t.text_dim);
|
||||
}
|
||||
for (sc, label, scope) in list.into_iter().take(9) {
|
||||
let col = if scope == APP_SCOPE { APP_COLOR } else { PANEL_COLOR };
|
||||
@@ -411,12 +412,12 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
|
||||
yy += 17.0;
|
||||
}
|
||||
} else {
|
||||
p.text(info.left_top() + vec2(8.0, 6.0), Align2::LEFT_TOP, "Click a key to see its commands.", Tokens::ui(11.5), t.text_dim);
|
||||
p.text(info.left_top() + vec2(8.0, 6.0), Align2::LEFT_TOP, tr("Click a key to see its commands."), Tokens::ui(11.5), t.text_dim);
|
||||
}
|
||||
ui.add_space(4.0);
|
||||
// ---- search + command list + editor
|
||||
ui.horizontal(|ui| {
|
||||
let r = ui.add(egui::TextEdit::singleline(&mut st.query).hint_text("Search commands or shortcuts").desired_width(360.0));
|
||||
let r = ui.add(egui::TextEdit::singleline(&mut st.query).hint_text(tr("Search commands or shortcuts")).desired_width(360.0));
|
||||
app.auto.add("shortcuts.search", r.rect, "Search");
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
@@ -476,7 +477,7 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
|
||||
ui.vertical(|ui| {
|
||||
ui.set_width(310.0);
|
||||
let Some(b) = st.selected.as_ref().and_then(|k| table.find(k)).cloned() else {
|
||||
ui.label(RichText::new("Select a command to change its shortcut.").color(t.text_dim));
|
||||
ui.label(RichText::new(tr("Select a command to change its shortcut.")).color(t.text_dim));
|
||||
return;
|
||||
};
|
||||
ui.label(RichText::new(&b.label).font(Tokens::semibold(13.0)).color(t.tab_text_active));
|
||||
@@ -484,7 +485,7 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
|
||||
ui.add_space(6.0);
|
||||
let keys = table.keys.get(&b.key).cloned().unwrap_or_default();
|
||||
if keys.is_empty() {
|
||||
ui.label(RichText::new("No shortcut").color(t.text_dim));
|
||||
ui.label(RichText::new(tr("No shortcut")).color(t.text_dim));
|
||||
}
|
||||
for (n, k) in keys.iter().enumerate() {
|
||||
ui.horizontal(|ui| {
|
||||
@@ -509,7 +510,7 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
|
||||
st.recording = false;
|
||||
}
|
||||
let norm = normalize(&st.pending);
|
||||
let b2 = ui.add_enabled(norm.is_some(), egui::Button::new("Assign"));
|
||||
let b2 = ui.add_enabled(norm.is_some(), egui::Button::new(tr("Assign")));
|
||||
app.auto.add("shortcuts.assign", b2.rect, "Assign");
|
||||
if b2.clicked()
|
||||
&& let Some(k) = norm
|
||||
@@ -526,31 +527,31 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
|
||||
let conflicts = table.conflicts(&b, &k);
|
||||
if !conflicts.is_empty() {
|
||||
ui.add_space(4.0);
|
||||
ui.label(RichText::new(format!("⚠ {} is used by:", crate::menus::shortcut_text(&k))).color(t.warning));
|
||||
ui.label(RichText::new(tr_args("⚠ {} is used by:", &[&(crate::menus::shortcut_text(&k))])).color(t.warning));
|
||||
for c in conflicts.iter().take(4) {
|
||||
ui.label(RichText::new(format!(" {} ({})", c.label, c.scope)).color(t.warning));
|
||||
}
|
||||
}
|
||||
}
|
||||
ui.add_space(6.0);
|
||||
let r = ui.add_enabled(keys != b.defaults, egui::Button::new("Reset to Default"));
|
||||
let r = ui.add_enabled(keys != b.defaults, egui::Button::new(tr("Reset to Default")));
|
||||
app.auto.add("shortcuts.resetCommand", r.rect, "Reset to Default");
|
||||
if r.clicked() {
|
||||
run.push(("shortcuts.reset".into(), json!({"command": b.command, "params": b.params})));
|
||||
}
|
||||
if !b.defaults.is_empty() {
|
||||
let d: Vec<String> = b.defaults.iter().map(|k| crate::menus::shortcut_text(k)).collect();
|
||||
ui.label(RichText::new(format!("Default: {}", d.join(", "))).color(t.text_faint));
|
||||
ui.label(RichText::new(tr_args("Default: {}", &[&(d.join(", "))])).color(t.text_faint));
|
||||
}
|
||||
if active == DEFAULT_PRESET {
|
||||
ui.add_space(6.0);
|
||||
ui.label(RichText::new("Changing a shortcut creates a custom preset; the default stays as it is.").color(t.text_faint).small());
|
||||
ui.label(RichText::new(tr("Changing a shortcut creates a custom preset; the default stays as it is.")).color(t.text_faint).small());
|
||||
}
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
let r = ui.add(egui::Button::new(RichText::new(" OK ").color(Color32::WHITE)).fill(t.accent));
|
||||
let r = ui.add(egui::Button::new(RichText::new(tr(" OK ")).color(Color32::WHITE)).fill(t.accent));
|
||||
app.auto.add("shortcuts.close", r.rect, "OK");
|
||||
close |= r.clicked();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user