mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-31 07:38:24 +00:00
G9: integrate prerequisite panel label wiring (#1581)
This commit is contained in:
commit
b333f08728
17 files changed
+192
-30
No files matched your search
@@ -39,11 +39,24 @@ impl EffectcraftApp {
|
||||
out.push((panels::viewers::panel(id), t));
|
||||
}
|
||||
}
|
||||
let Some(comp) = self.session.active_comp() else { return out };
|
||||
if let Some(l) = self.session.state.selected_layers.first().and_then(|id| comp.layer(*id)) {
|
||||
if let Some(comp) = self.session.active_comp()
|
||||
&& let Some(l) = self.session.state.selected_layers.first().and_then(|id| comp.layer(*id))
|
||||
{
|
||||
out.push((PanelKind::EffectControls, format!("Effect Controls {}", l.name)));
|
||||
out.push((PanelKind::Properties, format!("Properties: {}", l.name)));
|
||||
}
|
||||
// The panels named for themselves, in the interface language. The Window menu has carried a
|
||||
// (`window.panel`, title) row for these in every catalog that exists, so the dock reads the
|
||||
// same pair instead of drawing `title()`'s English; a panel the menu does not list (`History`,
|
||||
// `ScriptUI Panel`) keeps its English name, and a tab that names a comp or a layer is listed
|
||||
// above and wins, being found first.
|
||||
for p in PanelKind::ALL {
|
||||
// The Window menu lists most of these; `Markers` is only in the panel catalog, and the
|
||||
// two panels in neither (`History`, `ScriptUI Panel`) keep their English names.
|
||||
let listed = crate::i18n::label(self, "window.panel", p.title());
|
||||
let label = if listed == p.title() { crate::i18n::tr(p.title()) } else { listed };
|
||||
out.push((p, label.to_string()));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
@@ -570,7 +583,7 @@ impl EffectcraftApp {
|
||||
}
|
||||
}
|
||||
// Ghost of the dragged tab.
|
||||
let g = painter.layout_no_wrap(p.title().to_string(), crate::theme::Tokens::ui(12.0), Color32::WHITE);
|
||||
let g = painter.layout_no_wrap(crate::i18n::label(self, "window.panel", p.title()).to_string(), crate::theme::Tokens::ui(12.0), Color32::WHITE);
|
||||
let gr = Rect::from_min_size(pos + vec2(12.0, 10.0), g.size() + vec2(16.0, 8.0));
|
||||
painter.rect_filled(gr, 4.0, t.focus.gamma_multiply(0.85));
|
||||
painter.galley(gr.min + vec2(8.0, 4.0), g, Color32::WHITE);
|
||||
|
||||
@@ -6961,6 +6961,35 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// The dock's tab titles are `PanelKind::title()`; the panels the Window menu lists are drawn in
|
||||
/// the interface language through their `(window.panel, title)` rows. `History` and `ScriptUI
|
||||
/// Panel` are in no menu and keep their English names, which is what this pins.
|
||||
#[test]
|
||||
fn the_dock_draws_every_panel_it_can_in_the_interface_language() {
|
||||
use crate::dock::PanelKind;
|
||||
const WITHOUT_A_ROW: &[&str] = &["History", "ScriptUI Panel"];
|
||||
for (name, catalog, code) in [("ja", JAPANESE, "ja"), ("zh-hans", SIMPLIFIED_CHINESE, "zh-hans"), ("zh-hant", TRADITIONAL_CHINESE, "zh-hant")] {
|
||||
// The dock asks both catalogs, so a title may be named by either.
|
||||
ui::set_current(code);
|
||||
for p in PanelKind::ALL {
|
||||
let title = p.title();
|
||||
let in_menu = catalog.iter().any(|(c, en, _)| *c == "window.panel" && *en == title);
|
||||
let in_panel = ui::tr(title) != title;
|
||||
assert_eq!(
|
||||
in_menu || in_panel,
|
||||
!WITHOUT_A_ROW.contains(&title),
|
||||
"{name}: {title} is {}named by a catalog and {}in the exception list",
|
||||
if in_menu || in_panel { "" } else { "not " },
|
||||
if WITHOUT_A_ROW.contains(&title) { "" } else { "not " }
|
||||
);
|
||||
}
|
||||
}
|
||||
ui::set_current("en");
|
||||
// The dock asks the catalog rather than drawing the English name.
|
||||
let dock_ui = include_str!("dock_ui.rs");
|
||||
assert!(dock_ui.contains(r#"crate::i18n::label(self, "window.panel", p.title())"#), "dock_ui.rs no longer asks the catalog for the panel titles");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zh_hans_translates_labels_computed_titles_and_keeps_names_verbatim() {
|
||||
let mut app = EffectcraftApp::new(effectcraft_engine::Session::default());
|
||||
|
||||
@@ -1370,6 +1370,21 @@ const SIMPLIFIED_CHINESE: &[(&str, &str)] = &[
|
||||
("Total Offset", "总偏移"),
|
||||
("Per Layer", "每个图层"),
|
||||
("Track", "跟踪"),
|
||||
("Apply", "应用"),
|
||||
("Analyze", "分析"),
|
||||
("Create Camera", "创建摄像机"),
|
||||
("Add Point", "添加点"),
|
||||
("Remove Point", "移除点"),
|
||||
("Render", "渲染"),
|
||||
("Save Current as Template…", "将当前项存为模板…"),
|
||||
("New Composition From Footage", "从素材新建合成"),
|
||||
("Search effects and presets", "搜索效果和预设"),
|
||||
("Search project", "搜索项目"),
|
||||
("Search", "搜索"),
|
||||
("(no composition)", "没有合成"),
|
||||
("per second", "每秒"),
|
||||
("Waveform", "波形"),
|
||||
("steps", "步"),
|
||||
];
|
||||
|
||||
const TRADITIONAL_CHINESE: &[(&str, &str)] = &[
|
||||
@@ -2099,6 +2114,21 @@ const TRADITIONAL_CHINESE: &[(&str, &str)] = &[
|
||||
("Total Offset", "總偏移"),
|
||||
("Per Layer", "每個圖層"),
|
||||
("Track", "追蹤"),
|
||||
("Apply", "套用"),
|
||||
("Analyze", "分析"),
|
||||
("Create Camera", "建立攝影機"),
|
||||
("Add Point", "添加點"),
|
||||
("Remove Point", "移除點"),
|
||||
("Render", "算圖"),
|
||||
("Save Current as Template…", "將目前項目存為範本…"),
|
||||
("New Composition From Footage", "從素材新增合成"),
|
||||
("Search effects and presets", "搜尋效果與預設"),
|
||||
("Search project", "搜尋專案"),
|
||||
("Search", "搜尋"),
|
||||
("(no composition)", "沒有合成"),
|
||||
("per second", "每秒"),
|
||||
("Waveform", "波形"),
|
||||
("steps", "步驟"),
|
||||
];
|
||||
|
||||
/// Ukrainian (uk): original translations of the same interface rows as the Chinese catalogs.
|
||||
@@ -4478,6 +4508,89 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Labels drawn through a helper's *argument* or through `Painter::text` are invisible to the
|
||||
/// scan above, which only looks at a literal right after the call token: a `widgets::` helper
|
||||
/// takes its label as its second or third argument, and `Painter::text` takes the string third.
|
||||
/// Both shapes are checked here, so an English literal there is caught like any other.
|
||||
#[test]
|
||||
fn helper_and_painter_labels_are_translated() {
|
||||
/// The `widgets.rs` helpers that draw a label, and which argument holds it.
|
||||
const HELPER_LABELS: &[(&str, usize)] =
|
||||
&[("text_button", 2), ("search_field", 3), ("text_field", 3), ("dropdown", 2), ("dropdown_tip", 2), ("text_fit", 3), ("check_label", 1)];
|
||||
/// Literals that draw as they are: a brand, a value, a separator, a unit, and the kerning
|
||||
/// sample pair the Character panel shows.
|
||||
const VERBATIM: &[&str] = &["EffectCraft", "32", "dB", "···", "×", "V/A"];
|
||||
|
||||
/// The top-level arguments of a call, given the text after its `(`.
|
||||
fn args_of(s: &str) -> Vec<&str> {
|
||||
let (mut out, mut depth, mut start, mut instr) = (Vec::new(), 0i32, None, false);
|
||||
for (i, c) in s.char_indices() {
|
||||
match c {
|
||||
'"' if !instr => {
|
||||
instr = true;
|
||||
if start.is_none() {
|
||||
start = Some(i);
|
||||
}
|
||||
}
|
||||
'"' => instr = false,
|
||||
'(' | '[' | '{' if !instr => depth += 1,
|
||||
')' | ']' | '}' if !instr => {
|
||||
if depth == 0 {
|
||||
break;
|
||||
}
|
||||
depth -= 1;
|
||||
}
|
||||
',' if !instr && depth == 0 => {
|
||||
if let Some(st) = start {
|
||||
out.push(s[st..i].trim());
|
||||
}
|
||||
start = None;
|
||||
}
|
||||
c if !instr && !c.is_whitespace() && start.is_none() => start = Some(i),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if let Some(st) = start {
|
||||
out.push(s[st..].trim());
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
for (path, source) in CONVERTED {
|
||||
// The test modules of these files draw nothing; they hold sample strings.
|
||||
let source = source.split("#[cfg(test)]").next().unwrap_or(source);
|
||||
for (n, line) in source.lines().enumerate() {
|
||||
let code = line.split("//").next().unwrap_or(line);
|
||||
let check = |args: Vec<&str>, index: usize, what: &str| {
|
||||
let Some(arg) = args.get(index) else { return };
|
||||
// A single character is a symbol or a glyph, not interface text - the same
|
||||
// exemption the call-shape scan above makes.
|
||||
let inner = arg.strip_prefix('"').and_then(|a| a.strip_suffix('"')).filter(|i| !i.contains('"'));
|
||||
match inner {
|
||||
Some(text) if text.chars().count() >= 2 && !VERBATIM.contains(&text) => {
|
||||
panic!("{path}:{}: the interface text {arg} that {what} draws is not in the catalog", n + 1);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
};
|
||||
for (call, index) in HELPER_LABELS {
|
||||
let marker = format!("widgets::{call}(");
|
||||
let mut from = 0;
|
||||
while let Some(at) = code[from..].find(&marker) {
|
||||
let at = from + at + marker.len();
|
||||
check(args_of(&code[at..]), *index, &format!("widgets::{call}"));
|
||||
from = at;
|
||||
}
|
||||
}
|
||||
let mut from = 0;
|
||||
while let Some(at) = code[from..].find(".text(") {
|
||||
from = at + 6;
|
||||
check(args_of(&code[from..]), 2, "Painter::text");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The Settings pages come from the engine as data, so the literal scan above cannot see them:
|
||||
/// every title, section, label, choice, button, note and unit they draw needs a Chinese row
|
||||
/// (#1178). Language names are written in their own language, and a few labels read the same
|
||||
|
||||
@@ -109,7 +109,7 @@ pub fn wiggler(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
app.ui.anim_tools = st.clone();
|
||||
let b = Rect::from_min_size(pos2(rect.max.x - 92.0, y), vec2(80.0, 24.0));
|
||||
let enabled = app.session.state.selected_keys.len() >= 2;
|
||||
if widgets::text_button(ui, b, "Apply", enabled, &t, egui::Id::new("wg-apply")).clicked() {
|
||||
if widgets::text_button(ui, b, crate::i18n::tr("Apply"), enabled, &t, egui::Id::new("wg-apply")).clicked() {
|
||||
let params = json!({
|
||||
"apply": if st.wiggle_spatial { "spatial" } else { "temporal" },
|
||||
"noise": if st.wiggle_smooth { "smooth" } else { "jagged" },
|
||||
@@ -150,7 +150,7 @@ pub fn smoother(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
y += ROW + 8.0;
|
||||
let b = Rect::from_min_size(pos2(rect.max.x - 92.0, y), vec2(80.0, 24.0));
|
||||
let enabled = app.session.state.selected_keys.len() >= 3;
|
||||
if widgets::text_button(ui, b, "Apply", enabled, &t, egui::Id::new("sm-apply")).clicked() {
|
||||
if widgets::text_button(ui, b, crate::i18n::tr("Apply"), enabled, &t, egui::Id::new("sm-apply")).clicked() {
|
||||
apply(app, "keys.smooth", json!({"tolerance": tol}));
|
||||
}
|
||||
app.auto.add("smoother.apply", b, "Apply");
|
||||
@@ -321,7 +321,7 @@ pub fn mask_interpolation(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rec
|
||||
changes.insert("keyframeRate".into(), json!(nr));
|
||||
}
|
||||
} else {
|
||||
p.text(pos2(xv + 78.0, y + 9.0), Align2::LEFT_CENTER, "per second", Tokens::ui(12.0), t.text_dim);
|
||||
p.text(pos2(xv + 78.0, y + 9.0), Align2::LEFT_CENTER, crate::i18n::tr("per second"), Tokens::ui(12.0), t.text_dim);
|
||||
}
|
||||
y += ROW;
|
||||
let mut check = |app: &mut EffectcraftApp, y: f32, key: &str, text: &str, on: bool| {
|
||||
@@ -392,7 +392,7 @@ pub fn mask_interpolation(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rec
|
||||
}
|
||||
let b = Rect::from_min_size(pos2(rect.max.x - 92.0, y), vec2(80.0, 24.0));
|
||||
let enabled = app.session.state.selected_keys.len() >= 2;
|
||||
if widgets::text_button(ui, b, "Apply", enabled, &t, egui::Id::new("mi-apply")).clicked() {
|
||||
if widgets::text_button(ui, b, crate::i18n::tr("Apply"), enabled, &t, egui::Id::new("mi-apply")).clicked() {
|
||||
match app.session.execute("mask.interpolate", json!({})) {
|
||||
Ok(r) => app.ui.status = format!("Mask Interpolation: {} keyframes added ({} vertices)", r["keys"], r["vertices"]),
|
||||
Err(e) => app.ui.status = e.to_string(),
|
||||
|
||||
@@ -413,12 +413,12 @@ pub fn editor(
|
||||
let b1 = Rect::from_min_size(pos2(r.min.x + 24.0, r.min.y + 4.0), vec2(84.0, 22.0));
|
||||
let b2 = Rect::from_min_size(pos2(b1.max.x + 8.0, b1.min.y), vec2(84.0, 22.0));
|
||||
let b3 = Rect::from_min_size(pos2(b2.max.x + 8.0, b1.min.y), vec2(110.0, 22.0));
|
||||
if widgets::text_button(ui, b1, "Analyze", false, &t, egui::Id::new(("ct-analyze", g.uid))).clicked() && !busy {
|
||||
if widgets::text_button(ui, b1, crate::i18n::tr("Analyze"), false, &t, egui::Id::new(("ct-analyze", g.uid))).clicked() && !busy {
|
||||
actions.push(("camera.analyze".into(), me.clone()));
|
||||
}
|
||||
app.auto.add(&format!("effectControls.cameraTracker.{}.analyze", g.uid), b1, "Analyze");
|
||||
if running {
|
||||
if widgets::text_button(ui, b2, "Cancel", false, &t, egui::Id::new(("ct-cancel", g.uid))).clicked() {
|
||||
if widgets::text_button(ui, b2, crate::i18n::tr("Cancel"), false, &t, egui::Id::new(("ct-cancel", g.uid))).clicked() {
|
||||
actions.push(("camera.cancel".into(), json!({})));
|
||||
}
|
||||
app.auto.add(&format!("effectControls.cameraTracker.{}.cancel", g.uid), b2, "Cancel");
|
||||
@@ -426,7 +426,7 @@ pub fn editor(
|
||||
let params = effectcraft_engine::camera_track::static_params(g);
|
||||
let solve = ct::solve(¶ms);
|
||||
if solve.is_some() {
|
||||
if widgets::text_button(ui, b3, "Create Camera", false, &t, egui::Id::new(("ct-camera", g.uid))).clicked() {
|
||||
if widgets::text_button(ui, b3, crate::i18n::tr("Create Camera"), false, &t, egui::Id::new(("ct-camera", g.uid))).clicked() {
|
||||
actions.push(("camera.create".into(), me.clone()));
|
||||
}
|
||||
app.auto.add(&format!("effectControls.cameraTracker.{}.createCamera", g.uid), b3, "Create Camera");
|
||||
|
||||
@@ -756,12 +756,12 @@ fn warp_editor(app: &mut EffectcraftApp, ui: &mut egui::Ui, p: &egui::Painter, l
|
||||
let busy = app.session.is_warp_analyzing();
|
||||
let b1 = Rect::from_min_size(pos2(r.min.x + 24.0, r.min.y + 4.0), vec2(84.0, 22.0));
|
||||
let b2 = Rect::from_min_size(pos2(b1.max.x + 8.0, b1.min.y), vec2(84.0, 22.0));
|
||||
if widgets::text_button(ui, b1, "Analyze", false, &t, egui::Id::new(("warp-analyze", g.uid))).clicked() && !busy {
|
||||
if widgets::text_button(ui, b1, crate::i18n::tr("Analyze"), false, &t, egui::Id::new(("warp-analyze", g.uid))).clicked() && !busy {
|
||||
actions.push(("warp.analyze".into(), json!({"layer": layer.id.0, "effect": g.uid})));
|
||||
}
|
||||
app.auto.add(&format!("effectControls.warp.{}.analyze", g.uid), b1, "Analyze");
|
||||
if running {
|
||||
if widgets::text_button(ui, b2, "Cancel", false, &t, egui::Id::new(("warp-cancel", g.uid))).clicked() {
|
||||
if widgets::text_button(ui, b2, crate::i18n::tr("Cancel"), false, &t, egui::Id::new(("warp-cancel", g.uid))).clicked() {
|
||||
actions.push(("warp.cancel".into(), json!({})));
|
||||
}
|
||||
app.auto.add(&format!("effectControls.warp.{}.cancel", g.uid), b2, "Cancel");
|
||||
|
||||
@@ -277,7 +277,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
}
|
||||
let sr = Rect::from_min_size(rect.min + vec2(8.0, 8.0), vec2(rect.width() - 44.0, 24.0));
|
||||
let mut q = app.ui.effects_search.clone();
|
||||
widgets::search_field(ui, sr, &mut q, "Search effects and presets", &t);
|
||||
widgets::search_field(ui, sr, &mut q, crate::i18n::tr("Search effects and presets"), &t);
|
||||
app.ui.effects_search = q.clone();
|
||||
app.auto.add("effects.search", sr, "Search");
|
||||
// Contents menu.
|
||||
|
||||
@@ -250,7 +250,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
let p = ui.painter().with_clip_rect(rect);
|
||||
p.rect_filled(rect, 0.0, t.tl_bg);
|
||||
let Some(rid) = root(app) else {
|
||||
p.text(rect.center(), Align2::CENTER_CENTER, "(no composition)", Tokens::ui(12.0), t.text_faint);
|
||||
p.text(rect.center(), Align2::CENTER_CENTER, crate::i18n::tr("(no composition)"), Tokens::ui(12.0), t.text_faint);
|
||||
return;
|
||||
};
|
||||
// ---- toolbar: switches and direction.
|
||||
|
||||
@@ -786,19 +786,19 @@ fn reshape_buttons(
|
||||
let b1 = Rect::from_min_size(pos2(r.min.x + 24.0, r.min.y + 4.0), vec2(84.0, 22.0));
|
||||
let b2 = Rect::from_min_size(pos2(b1.max.x + 8.0, b1.min.y), vec2(96.0, 22.0));
|
||||
let b3 = Rect::from_min_size(pos2(b2.max.x + 8.0, b1.min.y), vec2(56.0, 22.0));
|
||||
if widgets::text_button(ui, b1, "Add Point", false, &t, egui::Id::new(("reshape-add", euid))).clicked() {
|
||||
if widgets::text_button(ui, b1, crate::i18n::tr("Add Point"), false, &t, egui::Id::new(("reshape-add", euid))).clicked() {
|
||||
let mut v = pairs.clone();
|
||||
v.push(add_pair(&pairs));
|
||||
set_str(actions, layer, g, "correspondencePoints", format_pairs(&v), None);
|
||||
}
|
||||
app.auto.add(&format!("effectControls.effect.{euid}.reshape.add"), b1, "Add Point");
|
||||
if widgets::text_button(ui, b2, "Remove Point", false, &t, egui::Id::new(("reshape-remove", euid))).clicked() && !pairs.is_empty() {
|
||||
if widgets::text_button(ui, b2, crate::i18n::tr("Remove Point"), false, &t, egui::Id::new(("reshape-remove", euid))).clicked() && !pairs.is_empty() {
|
||||
let mut v = pairs.clone();
|
||||
v.pop();
|
||||
set_str(actions, layer, g, "correspondencePoints", format_pairs(&v), None);
|
||||
}
|
||||
app.auto.add(&format!("effectControls.effect.{euid}.reshape.remove"), b2, "Remove Point");
|
||||
if widgets::text_button(ui, b3, "Clear", false, &t, egui::Id::new(("reshape-clear", euid))).clicked() && !pairs.is_empty() {
|
||||
if widgets::text_button(ui, b3, crate::i18n::tr("Clear"), false, &t, egui::Id::new(("reshape-clear", euid))).clicked() && !pairs.is_empty() {
|
||||
set_str(actions, layer, g, "correspondencePoints", String::new(), None);
|
||||
}
|
||||
app.auto.add(&format!("effectControls.effect.{euid}.reshape.clear"), b3, "Clear");
|
||||
|
||||
@@ -38,7 +38,7 @@ pub fn home_tab(app: &mut EffectcraftApp, ui: &mut egui::Ui, area: Rect) {
|
||||
p.circle_filled(badge.center(), 22.0, t.accent.gamma_multiply(0.25));
|
||||
p.circle_stroke(badge.center(), 22.0, Stroke::new(1.5, t.accent));
|
||||
p.text(badge.center() - vec2(0.0, 5.0), Align2::CENTER_CENTER, tut.steps.len().to_string(), Tokens::semibold(15.0), Color32::WHITE);
|
||||
p.text(badge.center() + vec2(0.0, 10.0), Align2::CENTER_CENTER, "steps", Tokens::ui(9.0), t.text_dim);
|
||||
p.text(badge.center() + vec2(0.0, 10.0), Align2::CENTER_CENTER, crate::i18n::tr("steps"), Tokens::ui(9.0), t.text_dim);
|
||||
let tx = badge.max.x + 16.0;
|
||||
p.text(pos2(tx, r.min.y + 22.0), Align2::LEFT_CENTER, &tut.title, Tokens::semibold(14.0), Color32::WHITE);
|
||||
p.text(pos2(tx, r.min.y + 40.0), Align2::LEFT_CENTER, format!("{} min", tut.minutes), Tokens::ui(11.0), t.text_faint);
|
||||
|
||||
@@ -11,7 +11,8 @@ 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, tr_args("{} — coming soon", &[&(p.title())]), Tokens::ui(12.0), t.text_faint);
|
||||
let name = crate::i18n::label(app, "window.panel", p.title());
|
||||
ui.painter().text(rect.center(), Align2::CENTER_CENTER, tr_args("{} — coming soon", &[&name]), Tokens::ui(12.0), t.text_faint);
|
||||
}
|
||||
|
||||
/// Preview panel: transport controls, the shortcut whose options are shown, and that
|
||||
|
||||
@@ -240,7 +240,7 @@ pub fn panel_menu_popup(app: &mut EffectcraftApp, ui: &mut egui::Ui) {
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
ui.label(egui::RichText::new(panel.title()).weak());
|
||||
ui.label(egui::RichText::new(crate::i18n::label(app, "window.panel", panel.title())).weak());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -443,7 +443,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
// Search.
|
||||
let sr = Rect::from_min_size(pos2(rect.min.x + 8.0, head.max.y + 2.0), vec2(rect.width() - 16.0, 22.0));
|
||||
let mut q = app.ui.project_search.clone();
|
||||
widgets::search_field(ui, sr, &mut q, "Search project", &t);
|
||||
widgets::search_field(ui, sr, &mut q, crate::i18n::tr("Search project"), &t);
|
||||
app.ui.project_search = q;
|
||||
app.auto.add("project.search", sr, "Search");
|
||||
// Column header: Name, Label, then the visible optional columns.
|
||||
|
||||
@@ -456,7 +456,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
let render_b = Rect::from_min_size(pos2(top.max.x - 12.0 - bw, top.min.y + 34.0), vec2(bw, 26.0));
|
||||
let stop_b = Rect::from_min_size(pos2(render_b.min.x - 8.0 - bw, render_b.min.y), vec2(bw, 26.0));
|
||||
let can_render = !rendering && queue.iter().any(RenderQueueItem::is_queued) && !available.is_empty();
|
||||
if widgets::text_button(ui, render_b, "Render", can_render, &t, egui::Id::new("rq-render")).clicked() && can_render {
|
||||
if widgets::text_button(ui, render_b, crate::i18n::tr("Render"), can_render, &t, egui::Id::new("rq-render")).clicked() && can_render {
|
||||
actions.push(("renderQueue.render", json!({"wait": false})));
|
||||
}
|
||||
app.auto.add("renderQueue.render", render_b, "Render");
|
||||
@@ -469,7 +469,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
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() {
|
||||
if widgets::text_button(ui, stop_b, crate::i18n::tr("Stop"), false, &t, egui::Id::new("rq-stop")).clicked() {
|
||||
actions.push(("renderQueue.stop", json!({})));
|
||||
}
|
||||
app.auto.add("renderQueue.stop", stop_b, "Stop");
|
||||
|
||||
@@ -69,7 +69,7 @@ pub fn home_tab(app: &mut EffectcraftApp, ui: &mut egui::Ui, area: Rect) {
|
||||
// Save the open project as a user template.
|
||||
let can_save = app.session.project.comps().next().is_some();
|
||||
let sr = Rect::from_min_size(pos2(area.max.x - 210.0, y - 4.0), vec2(210.0, 28.0));
|
||||
let save = widgets::text_button(ui, sr, "Save Current as Template…", false, &t, egui::Id::new("home-templates-save")).clicked();
|
||||
let save = widgets::text_button(ui, sr, crate::i18n::tr("Save Current as Template…"), false, &t, egui::Id::new("home-templates-save")).clicked();
|
||||
app.auto.add("home.templates.saveCurrent", sr, "Save Current as Template…");
|
||||
if save {
|
||||
if can_save {
|
||||
|
||||
@@ -1386,7 +1386,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
let t = app.tokens;
|
||||
let ctx = ui.ctx().clone();
|
||||
let Some(cid) = app.session.active_comp_id() else {
|
||||
ui.painter().text(rect.center(), Align2::CENTER_CENTER, "(no composition)", Tokens::ui(12.0), t.text_faint);
|
||||
ui.painter().text(rect.center(), Align2::CENTER_CENTER, crate::i18n::tr("(no composition)"), Tokens::ui(12.0), t.text_faint);
|
||||
// Dropping footage into an empty Timeline should create its first composition,
|
||||
// just like dropping onto the Project panel's New Composition button (#649).
|
||||
if let Some(crate::panels::DragPayload::Item(dragged)) = egui::DragAndDrop::payload::<crate::panels::DragPayload>(&ctx).as_deref()
|
||||
@@ -1517,7 +1517,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
}
|
||||
let sr = Rect::from_min_size(pos2(tc_rect.max.x + 14.0, top + 12.0), vec2((left_w - 420.0).clamp(120.0, 220.0), 22.0));
|
||||
let mut search = app.ui.timeline.search.clone();
|
||||
widgets::search_field(ui, sr, &mut search, "Search", &t);
|
||||
widgets::search_field(ui, sr, &mut search, crate::i18n::tr("Search"), &t);
|
||||
app.ui.timeline.search = search;
|
||||
app.auto.add("timeline.search", sr, "Search");
|
||||
let mut bx = rect.min.x + left_w - 10.0;
|
||||
@@ -2518,7 +2518,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
RowKind::Waveform { item } => {
|
||||
lp.rect_filled(left, 0.0, if ri % 2 == 0 { t.row } else { t.row_alt });
|
||||
let indent = cw.name + 6.0 + 14.0 * row.depth as f32;
|
||||
lp.text(pos2(indent + 10.0, cy), Align2::LEFT_CENTER, "Waveform", Tokens::ui(12.0), t.text);
|
||||
lp.text(pos2(indent + 10.0, cy), Align2::LEFT_CENTER, crate::i18n::tr("Waveform"), Tokens::ui(12.0), t.text);
|
||||
let wr = Rect::from_min_max(pos2(graph_x0, r.min.y), r.max);
|
||||
gp.rect_filled(wr, 0.0, t.tl_bg);
|
||||
app.auto.add(&format!("timeline.layer.{}.waveform", layer.id.0), wr, "Waveform");
|
||||
|
||||
@@ -745,7 +745,13 @@ fn locked_pane(app: &mut EffectcraftApp, ui: &mut egui::Ui, full: Rect, bg: Colo
|
||||
// Close the locked viewer.
|
||||
let close = Rect::from_min_size(pos2(r.max.x - 26.0, r.min.y + 4.0), vec2(20.0, 20.0));
|
||||
let resp = ui.interact(close, egui::Id::new("viewer-locked-close"), Sense::click());
|
||||
p.text(close.center(), Align2::CENTER_CENTER, "\u{00d7}", Tokens::ui(14.0), if resp.hovered() { Color32::WHITE } else { Color32::from_white_alpha(170) });
|
||||
p.text(
|
||||
close.center(),
|
||||
Align2::CENTER_CENTER,
|
||||
crate::i18n::tr("\u{00d7}"),
|
||||
Tokens::ui(14.0),
|
||||
if resp.hovered() { Color32::WHITE } else { Color32::from_white_alpha(170) },
|
||||
);
|
||||
app.auto.add("viewer.locked.close", close, "Close Locked Viewer");
|
||||
if resp.clicked() {
|
||||
let _ = app.session.execute("view.closeLockedViewer", serde_json::json!({}));
|
||||
@@ -2534,12 +2540,12 @@ fn empty_state(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
let c = rect.center();
|
||||
p.text(c - vec2(0.0, 40.0), Align2::CENTER_CENTER, crate::i18n::tr("Composition"), Tokens::semibold(16.0), t.text);
|
||||
let b1 = Rect::from_center_size(c + vec2(0.0, 0.0), vec2(220.0, 30.0));
|
||||
if widgets::text_button(ui, b1, "New Composition", true, &t, egui::Id::new("empty-newcomp")).clicked() {
|
||||
if widgets::text_button(ui, b1, crate::i18n::tr("New Composition"), true, &t, egui::Id::new("empty-newcomp")).clicked() {
|
||||
crate::panels::dialogs::open_new_comp(app);
|
||||
}
|
||||
app.auto.add("viewer.empty.newComp", b1, "New Composition");
|
||||
let b2 = Rect::from_center_size(c + vec2(0.0, 40.0), vec2(220.0, 30.0));
|
||||
if widgets::text_button(ui, b2, "New Composition From Footage", false, &t, egui::Id::new("empty-fromfootage")).clicked() {
|
||||
if widgets::text_button(ui, b2, crate::i18n::tr("New Composition From Footage"), false, &t, egui::Id::new("empty-fromfootage")).clicked() {
|
||||
let _ = crate::menus::invoke(app, &ui.ctx().clone(), "file.import", json!({}));
|
||||
}
|
||||
app.auto.add("viewer.empty.import", b2, "New Composition From Footage");
|
||||
|
||||
Reference in new issue
Block a user