panels: the timeline, viewer, project and layer panels read their text from the catalog

Timeline switches and column headers, the viewer's tools and guides, the Project panel's menus,
tooltips and the layer, properties, graph, marker, tracker and camera-tracker panels now go through
`crate::i18n`.
This commit is contained in:
FeiLiuEM
2026-10-09 20:17:05 +08:00
parent add2e977f6
commit a15a436647
20 changed files with 225 additions and 159 deletions
+14 -13
View File
@@ -1,6 +1,7 @@
//! The Tools bar: home, the tool slots, tool options, snapping, workspaces and the community
//! buttons (Discord is always one click away).
use crate::i18n::tr;
use egui::{Align2, Color32, Rect, Sense, Stroke, StrokeKind, pos2, vec2};
use serde_json::json;
@@ -27,14 +28,14 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
paint_logo(&p, brand);
let bresp = ui.interact(brand, egui::Id::new("brand"), Sense::click());
app.auto.add("header.about", brand, "About EffectCraft");
if bresp.on_hover_text("About EffectCraft").clicked() {
if bresp.on_hover_text(tr("About EffectCraft")).clicked() {
app.dialog = Some(Dialog::About);
}
x += 32.0;
// Home.
let home = Rect::from_min_size(pos2(x, cy - 13.0), vec2(26.0, 26.0));
if widgets::icon_button(ui, home, Icon::Home, app.ui.start_screen, &t, egui::Id::new("tool-home")).on_hover_text("Home").clicked() {
if widgets::icon_button(ui, home, Icon::Home, app.ui.start_screen, &t, egui::Id::new("tool-home")).on_hover_text(tr("Home")).clicked() {
app.ui.start_screen = !app.ui.start_screen;
}
app.auto.add("header.home", home, "Home");
@@ -171,7 +172,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
app.ui.snapping = app.session.state.snapping;
}
app.auto.add("header.snapping", snap, "Snapping");
let label = p.text(pos2(snap.max.x + 4.0, cy), Align2::LEFT_CENTER, "Snapping", Tokens::ui(12.0), t.text_dim);
let label = p.text(pos2(snap.max.x + 4.0, cy), Align2::LEFT_CENTER, tr("Snapping"), Tokens::ui(12.0), t.text_dim);
snapping_options(app, ui, Rect::from_min_size(pos2(label.max.x + 2.0, cy - 9.0), vec2(18.0, 18.0)));
// Right side: community buttons, workspaces.
@@ -181,9 +182,9 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
let dc = Color32::from_rgb(0x58, 0x65, 0xf2);
p.rect_filled(discord, 13.0, if dresp.hovered() { dc.gamma_multiply(1.2) } else { dc });
icons::paint(&p, Rect::from_center_size(pos2(discord.min.x + 16.0, cy), vec2(14.0, 14.0)), Icon::Chat, Color32::WHITE);
p.text(pos2(discord.min.x + 28.0, cy), Align2::LEFT_CENTER, "Discord", Tokens::semibold(12.0), Color32::WHITE);
p.text(pos2(discord.min.x + 28.0, cy), Align2::LEFT_CENTER, tr("Discord"), Tokens::semibold(12.0), Color32::WHITE);
app.auto.add("header.discord", discord, "Join the ArtCraft Discord");
if dresp.on_hover_text("Join the ArtCraft community on Discord").clicked() {
if dresp.on_hover_text(tr("Join the ArtCraft community on Discord")).clicked() {
let _ = app.session.execute("help.discord", json!({}));
}
rx = discord.min.x - 6.0;
@@ -256,7 +257,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
fn snapping_options(app: &mut EffectcraftApp, ui: &mut egui::Ui, r: Rect) {
let t = app.tokens;
let pop = egui::Id::new("snap-options");
if widgets::icon_button(ui, r, Icon::ChevronDown, false, &t, egui::Id::new("snap-options-button")).on_hover_text("Snapping options").clicked() {
if widgets::icon_button(ui, r, Icon::ChevronDown, false, &t, egui::Id::new("snap-options-button")).on_hover_text(tr("Snapping options")).clicked() {
widgets::open_popup(ui, pop);
}
app.auto.add("header.snappingOptions", r, "Snapping options");
@@ -409,13 +410,13 @@ fn paint_options_popup(app: &mut EffectcraftApp, ui: &mut egui::Ui, key: &str, t
}
}
p.text(row(33.0) + vec2(134.0, 0.0), Align2::LEFT_CENTER, tp.kind.label(), Tokens::ui(11.5), t.text_dim);
p.text(row(66.0), Align2::LEFT_CENTER, "Blend Mode:", Tokens::ui(12.0), t.text_dim);
p.text(row(66.0), Align2::LEFT_CENTER, tr("Blend Mode:"), Tokens::ui(12.0), t.text_dim);
blend_rect = Rect::from_min_size(row(56.0) + vec2(84.0, 0.0), vec2(150.0, 20.0));
if widgets::dropdown(ui, blend_rect, tp.blend.label(), &t, id.with("blend-dd")).clicked() {
widgets::open_popup(ui, blend_pop);
}
app.auto.add(&format!("header.{key}Options.blend"), blend_rect, "Blend Mode");
p.text(row(98.0), Align2::LEFT_CENTER, "Opacity:", Tokens::ui(12.0), t.text_dim);
p.text(row(98.0), Align2::LEFT_CENTER, tr("Opacity:"), Tokens::ui(12.0), t.text_dim);
let (or, v, _) = widgets::hot_number_at(ui, row(89.0) + vec2(84.0, 0.0), id.with("opacity"), tp.opacity, 0.5, (0.0, 100.0), 0, "%", &t);
app.auto.add(&format!("header.{key}Options.opacity"), or, "Opacity");
if let Some(v) = v {
@@ -485,14 +486,14 @@ fn paint_swatch(p: &egui::Painter, r: Rect, paint: &ToolPaint, hovered: bool, t:
fn puppet_options(app: &mut EffectcraftApp, ui: &mut egui::Ui, p: &egui::Painter, mut x: f32, cy: f32) -> f32 {
let t = app.tokens;
let o = app.session.state.puppet.clone();
p.text(pos2(x, cy), Align2::LEFT_CENTER, "Mesh:", Tokens::ui(12.0), t.text_dim);
p.text(pos2(x, cy), Align2::LEFT_CENTER, tr("Mesh:"), Tokens::ui(12.0), t.text_dim);
x += 40.0;
let show = Rect::from_min_size(pos2(x, cy - 10.0), vec2(20.0, 20.0));
if widgets::checkbox(ui, show, o.show_mesh, &t, egui::Id::new("puppet-show-mesh")).clicked() {
let _ = app.session.execute("puppet.mesh", json!({"showMesh": !o.show_mesh}));
}
app.auto.add("header.puppet.showMesh", show, "Show mesh");
p.text(pos2(show.max.x + 2.0, cy), Align2::LEFT_CENTER, "Show", Tokens::ui(12.0), t.text_dim);
p.text(pos2(show.max.x + 2.0, cy), Align2::LEFT_CENTER, tr("Show"), Tokens::ui(12.0), t.text_dim);
x = show.max.x + 44.0;
for (label, key, v, range) in [("Expansion:", "expansion", o.expansion, (-100.0, 200.0)), ("Density:", "density", o.density, (0.0, 100.0))] {
p.text(pos2(x, cy), Align2::LEFT_CENTER, label, Tokens::ui(12.0), t.text_dim);
@@ -508,7 +509,7 @@ fn puppet_options(app: &mut EffectcraftApp, ui: &mut egui::Ui, p: &egui::Painter
let label = "Record Options...";
let w = 104.0;
let r = Rect::from_min_size(pos2(x, cy - 10.0), vec2(w, 20.0));
let resp = ui.interact(r, egui::Id::new("puppet-record-options"), egui::Sense::click()).on_hover_text("⌘/Ctrl-drag a pin to record its motion");
let resp = ui.interact(r, egui::Id::new("puppet-record-options"), egui::Sense::click()).on_hover_text(tr("⌘/Ctrl-drag a pin to record its motion"));
p.rect_stroke(r, 3.0, egui::Stroke::new(1.0, if resp.hovered() { t.accent } else { t.field_border }), egui::StrokeKind::Inside);
p.text(r.center(), Align2::CENTER_CENTER, label, Tokens::ui(11.5), t.text);
app.auto.add("header.puppet.recordOptions", r, label);
@@ -523,7 +524,7 @@ fn puppet_options(app: &mut EffectcraftApp, ui: &mut egui::Ui, p: &egui::Painter
let r = Rect::from_min_size(pos2(r.max.x + 8.0, cy - 10.0), vec2(w, 20.0));
let resp = ui
.interact(r, egui::Id::new("puppet-follow"), egui::Sense::click())
.on_hover_text("Select the leader pin, then Shift-click the pins that should trail it (hair, cloth, tails)");
.on_hover_text(tr("Select the leader pin, then Shift-click the pins that should trail it (hair, cloth, tails)"));
p.rect_stroke(r, 3.0, egui::Stroke::new(1.0, if resp.hovered() { t.accent } else { t.field_border }), egui::StrokeKind::Inside);
p.text(r.center(), Align2::CENTER_CENTER, label, Tokens::ui(11.5), t.text);
app.auto.add("header.puppet.follow", r, label);
@@ -544,7 +545,7 @@ fn ws_menu(app: &mut EffectcraftApp, ui: &mut egui::Ui) {
}
}
ui.separator();
if ui.button("Reset to Saved Layout").clicked() {
if ui.button(tr("Reset to Saved Layout")).clicked() {
let n = app.ui.workspace.clone();
app.set_workspace(&n);
ui.close();
+4 -3
View File
@@ -6,6 +6,7 @@
//! Start Capture, pressing in the viewer records the pointer path in real time while the comp
//! plays; releasing turns it into Position keys.
use crate::i18n::{tr, tr_args};
use egui::{Align2, Color32, Rect, Sense, Stroke, pos2, vec2};
use serde_json::json;
@@ -170,7 +171,7 @@ pub fn motion_sketch(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
label(&p, x0, y, "Smoothing:", &t);
st.sketch_smoothing = number(app, ui, pos2(xv, y), "motionSketch.smoothing", st.sketch_smoothing, 0.1, (0.0, 100.0), 1, "");
y += ROW + 2.0;
p.text(pos2(x0, y + 8.0), Align2::LEFT_CENTER, "Show:", Tokens::ui(12.0), t.text_dim);
p.text(pos2(x0, y + 8.0), Align2::LEFT_CENTER, tr("Show:"), Tokens::ui(12.0), t.text_dim);
y += ROW - 6.0;
st.sketch_wireframe = checkbox(app, ui, pos2(x0 + 8.0, y), "motionSketch.wireframe", "Wireframe", st.sketch_wireframe);
y += ROW - 4.0;
@@ -183,7 +184,7 @@ pub fn motion_sketch(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
let fr = c.frame_rate;
let (s, e) = (app.session.time(), c.work_area.1);
let tc = |x| crate::panels::timecode(&app.session, c, x);
p.text(pos2(x0, y + 8.0), Align2::LEFT_CENTER, format!("Start: {} Duration: {}", tc(s), tc(e - s)), Tokens::ui(11.0), t.text_faint);
p.text(pos2(x0, y + 8.0), Align2::LEFT_CENTER, tr_args("Start: {} Duration: {}", &[&(tc(s)), &(tc(e - s))]), Tokens::ui(11.0), t.text_faint);
let _ = fr;
y += ROW;
let b = Rect::from_min_size(pos2(x0, y), vec2(120.0, 24.0));
@@ -290,7 +291,7 @@ pub fn sketch_overlay(app: &mut EffectcraftApp, ui: &mut egui::Ui) {
ctx.data_mut(|d| d.insert_temp(capture_id(), c));
}
None => {
painter.text(area.center_top() + vec2(0.0, 18.0), Align2::CENTER_CENTER, "Motion Sketch: drag to record", Tokens::ui(12.0), Color32::WHITE);
painter.text(area.center_top() + vec2(0.0, 18.0), Align2::CENTER_CENTER, tr("Motion Sketch: drag to record"), Tokens::ui(12.0), Color32::WHITE);
}
}
}
@@ -13,6 +13,7 @@
//!
//! Everything is dispatched to the `camera.*` commands.
use crate::i18n::{tr, tr_args};
use std::sync::Arc;
use effectcraft_engine::Session;
@@ -297,7 +298,7 @@ pub fn viewer_hook(
let some = !pts.is_empty();
let many = pts.len() > 1;
let mut pick: Option<(&str, serde_json::Value)> = None;
if ui.add_enabled(some, egui::Button::new("Set Ground Plane and Origin")).clicked() {
if ui.add_enabled(some, egui::Button::new(tr("Set Ground Plane and Origin"))).clicked() {
pick = Some(("camera.setGroundPlane", with(None, false)));
}
ui.separator();
@@ -320,7 +321,7 @@ pub fn viewer_hook(
}
}
ui.separator();
if ui.add_enabled(some, egui::Button::new("Delete Selected Points")).clicked() {
if ui.add_enabled(some, egui::Button::new(tr("Delete Selected Points"))).clicked() {
pick = Some(("camera.deletePoints", with(None, false)));
}
if let Some((cmd, q)) = pick {
@@ -419,7 +420,7 @@ pub fn editor(
let status = match app.session.camera_progress() {
Some(pr) if running => pr.banner(),
_ => match &solve {
Some(sv) => format!("Method Used: {} Average Error: {:.2} pixels", sv.method_used.label(), sv.average_error),
Some(sv) => tr_args("Method Used: {} Average Error: {} pixels", &[&(sv.method_used.label()), &format!("{:.2}", sv.average_error)]),
None if ct::tracks(&params).is_some() && app.session.camera_pending.iter().any(|(_, l, u)| *l == layer.id && *u == g.uid) => {
"Waiting to solve".into()
}
+2 -1
View File
@@ -3,6 +3,7 @@
//! Clicking the message selects the layer and reveals the property in the timeline.
//! Automation ids: `viewer.exprErrors`, `viewer.exprErrors.prev`, `viewer.exprErrors.next`.
use crate::i18n::tr;
use effectcraft_engine::commands::expr_tools::{self, ExprError};
use effectcraft_engine::project::ItemId;
use egui::{Align2, Color32, Rect, Sense, pos2, vec2};
@@ -77,7 +78,7 @@ pub fn draw(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect, cid: ItemId
let comp_name = app.session.project.item(cid).map(|it| it.name.clone()).unwrap_or_default();
let e = c.errors[i].clone();
let msg_rect = Rect::from_min_max(pos2(rect.min.x + 26.0, rect.min.y), pos2(prev.min.x - 6.0, rect.max.y));
let resp = ui.interact(msg_rect, egui::Id::new("expr-bar-msg"), Sense::click()).on_hover_text("Click to reveal the expression in the timeline");
let resp = ui.interact(msg_rect, egui::Id::new("expr-bar-msg"), Sense::click()).on_hover_text(tr("Click to reveal the expression in the timeline"));
p.with_clip_rect(msg_rect).text(
pos2(msg_rect.min.x, rect.center().y),
Align2::LEFT_CENTER,
+3 -2
View File
@@ -6,6 +6,7 @@
//! [`build`] is pure and cycle-safe (a comp nested in itself is drawn once, with an edge back),
//! so agents get the same graph through `flowchart.graph`.
use crate::i18n::tr_args;
use std::collections::{BTreeMap, BTreeSet};
use effectcraft_engine::project::{ItemId, ItemKind, LayerSource, Project};
@@ -256,7 +257,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
let bar = Rect::from_min_size(rect.min, vec2(rect.width(), 30.0));
p.rect_filled(bar, 0.0, t.panel_bg);
let root_name = app.session.project.item(rid).map(|i| i.name.clone()).unwrap_or_default();
p.text(pos2(bar.min.x + 10.0, bar.center().y), Align2::LEFT_CENTER, format!("Flowchart: {root_name}"), Tokens::medium(12.0), t.text);
p.text(pos2(bar.min.x + 10.0, bar.center().y), Align2::LEFT_CENTER, tr_args("Flowchart: {}", &[&root_name]), Tokens::medium(12.0), t.text);
let mut x = bar.max.x - 8.0;
let mut cmds: Vec<(String, Value)> = vec![];
let o = app.ui.flowchart.clone();
@@ -363,7 +364,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
_ => {}
}
}
resp.on_hover_text(format!("{} — double-click to open, {}-click to reveal", n.label, if cfg!(target_os = "macos") { "Cmd" } else { "Ctrl" }));
resp.on_hover_text(tr_args("{} — double-click to open, {}-click to reveal", &[&n.label, &(if cfg!(target_os = "macos") { "Cmd" } else { "Ctrl" })]));
}
for (id, params) in cmds {
if let Err(e) = crate::menus::invoke(app, &ctx, &id, params) {
+5 -4
View File
@@ -3,6 +3,7 @@
//! Insert Edit — that cut the marked range into the active composition at its current time.
//! Everything runs `footage.*` commands (state in `EditorState::footage_panel`).
use crate::i18n::{tr, tr_args};
use effectcraft_engine::project::{FootageKind, ItemKind};
use effectcraft_engine::time::Tick;
use egui::{Align2, Color32, Rect, Sense, Stroke, pos2, vec2};
@@ -18,7 +19,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.pasteboard);
let Some(view) = app.session.state.footage_panel.clone() else {
p.text(rect.center(), Align2::CENTER_CENTER, "Double-click footage in the Project panel to open it here", Tokens::ui(12.0), t.text_faint);
p.text(rect.center(), Align2::CENTER_CENTER, tr("Double-click footage in the Project panel to open it here"), Tokens::ui(12.0), t.text_faint);
app.auto.add("footage.empty", rect, "Footage panel (empty)");
return;
};
@@ -38,7 +39,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
// Title strip.
let top = Rect::from_min_size(rect.min, vec2(rect.width(), 24.0));
p.rect_filled(top, 0.0, t.panel_bg);
p.text(pos2(top.min.x + 10.0, top.center().y), Align2::LEFT_CENTER, format!("Footage: {}", item.name), Tokens::semibold(12.0), t.text);
p.text(pos2(top.min.x + 10.0, top.center().y), Align2::LEFT_CENTER, tr_args("Footage: {}", &[&item.name]), Tokens::semibold(12.0), t.text);
app.auto.add("footage.title", top, &item.name);
// Bottom: ruler and controls.
// Rows, bottom up: Edit Target buttons, transport, ruler.
@@ -73,7 +74,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
let c = so.color;
p.rect_filled(area.shrink(20.0), 0.0, Color32::from_rgb((c[0] * 255.0) as u8, (c[1] * 255.0) as u8, (c[2] * 255.0) as u8));
} else {
p.text(area.center(), Align2::CENTER_CENTER, "No video", Tokens::ui(12.0), t.text_faint);
p.text(area.center(), Align2::CENTER_CENTER, tr("No video"), Tokens::ui(12.0), t.text_faint);
}
// Ruler: ticks, In/Out span, CTI; click or drag to scrub.
let x_of = |tt: Tick| ruler.min.x + (tt.seconds() / dur.seconds().max(1e-9)) as f32 * ruler.width();
@@ -139,7 +140,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
let ey = edit_row.min.y + 5.0;
let bx = (edit_row.max.x - 238.0).max(edit_row.min.x + 10.0);
if !target.is_empty() && bx > edit_row.min.x + 140.0 {
p.text(pos2(bx - 8.0, edit_row.center().y), Align2::RIGHT_CENTER, format!("Edit Target: {target}"), Tokens::ui(11.0), t.text_dim);
p.text(pos2(bx - 8.0, edit_row.center().y), Align2::RIGHT_CENTER, tr_args("Edit Target: {}", &[&target]), Tokens::ui(11.0), t.text_dim);
}
if kit::button(app, ui, Rect::from_min_size(pos2(bx, ey), vec2(110.0, 20.0)), "footage.rippleInsertEdit", "Ripple Insert Edit", false) && comp_ok {
kit::exec(app, "footage.rippleInsertEdit", json!({}));
+7 -1
View File
@@ -244,7 +244,13 @@ pub(crate) fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, p: &egui::Painte
gv += step;
}
if cs.is_empty() {
p.text(plot.center(), Align2::CENTER_CENTER, "Select animated properties to show them in the Graph Editor", Tokens::ui(12.0), t.text_faint);
p.text(
plot.center(),
Align2::CENTER_CENTER,
crate::i18n::tr("Select animated properties to show them in the Graph Editor"),
Tokens::ui(12.0),
t.text_faint,
);
}
// Show Reference Graph: the other graph type, faint, scaled to the plot.
+16 -3
View File
@@ -1,5 +1,6 @@
//! Info panel: colour and position under the pointer, plus the current selection.
use crate::i18n::{tr, tr_args};
use egui::{Align2, Color32, Rect, pos2, vec2};
use crate::theme::Tokens;
@@ -59,8 +60,14 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
if let Some(c) = comp {
let sel: Vec<String> = app.session.state.selected_layers.iter().filter_map(|id| c.layer(*id)).map(|l| l.name.clone()).collect();
let tc = crate::panels::timecode(&app.session, &c, app.session.time());
p.text(pos2(x0, y), Align2::LEFT_CENTER, if sel.is_empty() { "No layer selected".to_string() } else { sel.join(", ") }, Tokens::semibold(12.0), t.text);
p.text(pos2(x0, y + 18.0), Align2::LEFT_CENTER, format!("Time: {tc}"), Tokens::ui(12.0), t.text_dim);
p.text(
pos2(x0, y),
Align2::LEFT_CENTER,
if sel.is_empty() { tr("No layer selected").to_string() } else { sel.join(", ") },
Tokens::semibold(12.0),
t.text,
);
p.text(pos2(x0, y + 18.0), Align2::LEFT_CENTER, tr_args("Time: {}", &[&tc]), Tokens::ui(12.0), t.text_dim);
// One layer selected: Duration, In and Out like AE's Info panel.
let mut ly = y + 36.0;
if let [id] = app.session.state.selected_layers.as_slice()
@@ -93,7 +100,13 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
if app.session.prefs.composition.show_rendering_progress {
let ms = app.frames.last_ms.lock().map(|v| *v).unwrap_or(0.0);
let rq = app.session.render_job.as_ref().map(|_| " • Rendering queue…").unwrap_or("");
p.text(pos2(x0, ly), Align2::LEFT_CENTER, format!("Render: {ms:.0} ms • UI {:.0} fps{rq}", app.fps), Tokens::ui(11.5), t.text_faint);
p.text(
pos2(x0, ly),
Align2::LEFT_CENTER,
tr_args("Render: {} ms • UI {} fps{}", &[&format!("{ms:.0}"), &format!("{:.0}", app.fps), &rq]),
Tokens::ui(11.5),
t.text_faint,
);
}
if !app.session.state.selected_keys.is_empty() {
// One key: its property, time and value (as After Effects' Info panel shows them).
+4 -3
View File
@@ -11,6 +11,7 @@
//! overlay, the segmentation span bar and the view / Freeze buttons; the Roto Brush and Refine
//! Edge tools paint their strokes here (see [`super::roto_tool`]).
use crate::i18n::{tr, tr_args};
use effectcraft_engine::effects::paint;
use effectcraft_engine::project::{Layer, LayerId};
use effectcraft_engine::render::{EvalCtx, RenderOpts, Renderer};
@@ -67,7 +68,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
let p = ui.painter().with_clip_rect(rect);
let (Some(cid), Some(layer)) = (app.session.active_comp_id(), current_layer(app)) else {
p.rect_filled(rect, 0.0, t.pasteboard);
p.text(rect.center(), Align2::CENTER_CENTER, "Double-click a layer to open it in the Layer panel", Tokens::ui(12.0), t.text_faint);
p.text(rect.center(), Align2::CENTER_CENTER, tr("Double-click a layer to open it in the Layer panel"), Tokens::ui(12.0), t.text_faint);
app.auto.add("layerPanel.empty", rect, "Layer panel (empty)");
return;
};
@@ -80,12 +81,12 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
// Title strip.
let top = Rect::from_min_size(rect.min, vec2(rect.width(), 24.0));
p.rect_filled(top, 0.0, t.panel_bg);
p.text(pos2(top.min.x + 10.0, top.center().y), Align2::LEFT_CENTER, format!("Layer: {}", layer.name), Tokens::semibold(12.0), t.text);
p.text(pos2(top.min.x + 10.0, top.center().y), Align2::LEFT_CENTER, tr_args("Layer: {}", &[&layer.name]), Tokens::semibold(12.0), t.text);
app.auto.add("layerPanel.title", top, &layer.name);
// Bottom bar: View menu.
let bar = Rect::from_min_max(pos2(rect.min.x, rect.max.y - 30.0), rect.max);
p.rect_filled(bar, 0.0, t.panel_bg);
p.text(pos2(bar.min.x + 10.0, bar.center().y), Align2::LEFT_CENTER, "View:", Tokens::ui(12.0), t.text_dim);
p.text(pos2(bar.min.x + 10.0, bar.center().y), Align2::LEFT_CENTER, tr("View:"), Tokens::ui(12.0), t.text_dim);
let mut names = vec!["Masks".to_string()];
if let Some(fx) = layer.effects() {
names.extend(fx.groups().map(|g| g.name.clone()));
+19 -18
View File
@@ -8,6 +8,7 @@
//! composition marker bin, at the right end of the time ruler, adds a composition marker where it
//! is dropped. Every change is an engine command, so it is undoable and agent-drivable.
use crate::i18n::tr;
use effectcraft_engine::project::{Comp, Layer, Marker};
use effectcraft_engine::time::Tick;
use egui::{Align2, Color32, Pos2, Rect, Sense, Stroke, pos2, vec2};
@@ -126,46 +127,46 @@ pub fn dialog(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
let title = d.title.clone();
super::dialogs::modal(ctx, &title, vec2(520.0, 560.0), t, |ui| {
egui::Grid::new("marker-grid").num_columns(2).spacing([12.0, 8.0]).show(ui, |ui| {
ui.label("Time:");
ui.label(tr("Time:"));
let r = ui.add(egui::DragValue::new(&mut d.time).speed(0.01).range(0.0..=1e6).suffix(" s").max_decimals(3));
app.auto.add("dialog.marker.time", r.rect, "Time");
ui.end_row();
ui.label("Duration:");
ui.label(tr("Duration:"));
let r = ui.add(egui::DragValue::new(&mut d.duration).speed(0.01).range(0.0..=1e6).suffix(" s").max_decimals(3));
app.auto.add("dialog.marker.duration", r.rect, "Duration");
ui.end_row();
ui.label("Comment:");
ui.label(tr("Comment:"));
let r = ui.add(egui::TextEdit::multiline(&mut d.comment).desired_rows(3).desired_width(320.0));
app.auto.add("dialog.marker.comment", r.rect, "Comment");
ui.end_row();
ui.label("Chapter:");
ui.label(tr("Chapter:"));
text_field(ui, app, "dialog.marker.chapter", &mut d.chapter, 320.0);
ui.end_row();
ui.label("URL:");
ui.label(tr("URL:"));
text_field(ui, app, "dialog.marker.url", &mut d.url, 320.0);
ui.end_row();
ui.label("Frame Target:");
ui.label(tr("Frame Target:"));
text_field(ui, app, "dialog.marker.frameTarget", &mut d.frame_target, 320.0);
ui.end_row();
});
ui.add_space(8.0);
let r = ui.checkbox(&mut d.cue, "Cue Point");
let r = ui.checkbox(&mut d.cue, tr("Cue Point"));
app.auto.add("dialog.marker.cue", r.rect, "Cue Point");
ui.add_enabled_ui(d.cue, |ui| {
ui.horizontal(|ui| {
let r = ui.radio_value(&mut d.cue_navigation, false, "Event");
let r = ui.radio_value(&mut d.cue_navigation, false, tr("Event"));
app.auto.add("dialog.marker.cue.event", r.rect, "Event");
let r = ui.radio_value(&mut d.cue_navigation, true, "Navigation");
let r = ui.radio_value(&mut d.cue_navigation, true, tr("Navigation"));
app.auto.add("dialog.marker.cue.navigation", r.rect, "Navigation");
ui.label("Name:");
ui.label(tr("Name:"));
text_field(ui, app, "dialog.marker.cue.name", &mut d.cue_name, 160.0);
});
let mut remove = None;
for (i, (k, v)) in d.cue_params.iter_mut().enumerate() {
ui.horizontal(|ui| {
ui.label("Parameter Name:");
ui.label(tr("Parameter Name:"));
text_field(ui, app, &format!("dialog.marker.cue.param.{i}.name"), k, 110.0);
ui.label("Value:");
ui.label(tr("Value:"));
text_field(ui, app, &format!("dialog.marker.cue.param.{i}.value"), v, 110.0);
let r = ui.small_button("−");
app.auto.add(&format!("dialog.marker.cue.param.{i}.remove"), r.rect, "Remove");
@@ -185,10 +186,10 @@ pub fn dialog(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
});
ui.add_space(8.0);
ui.horizontal(|ui| {
let r = ui.checkbox(&mut d.protected, "Protected Region");
let r = ui.checkbox(&mut d.protected, tr("Protected Region"));
app.auto.add("dialog.marker.protected", r.rect, "Protected Region");
ui.add_space(20.0);
ui.label("Label:");
ui.label(tr("Label:"));
let labels = effectcraft_engine::color::Label::ALL;
let cur = labels.get(d.label).copied().unwrap_or_default();
let r = egui::ComboBox::from_id_salt("dialog.marker.label").selected_text(app.session.prefs.label_name(cur)).show_ui(ui, |ui| {
@@ -202,14 +203,14 @@ pub fn dialog(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
});
ui.add_space(14.0);
ui.horizontal(|ui| {
let r = ui.button("Delete");
let r = ui.button(tr("Delete"));
app.auto.add("dialog.marker.delete", r.rect, "Delete");
delete = r.clicked();
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(app.tokens.accent));
let r = ui.add(egui::Button::new(egui::RichText::new(tr(" OK ")).color(Color32::WHITE)).fill(app.tokens.accent));
app.auto.add("dialog.marker.ok", r.rect, "OK");
ok = r.clicked();
let r = ui.button("Cancel");
let r = ui.button(tr("Cancel"));
app.auto.add("dialog.marker.cancel", r.rect, "Cancel");
cancel = r.clicked();
});
@@ -383,7 +384,7 @@ pub(crate) fn comp_markers(app: &mut EffectcraftApp, ui: &mut egui::Ui, comp: &C
/// one undo step. Dropped back on the bin or outside the time graph's width, nothing is added.
pub(crate) fn marker_bin(app: &mut EffectcraftApp, ui: &mut egui::Ui, comp: &Comp, tm: TMap, bin: Rect, strip: Rect) {
let t = app.tokens;
let resp = ui.interact(bin, egui::Id::new("marker-bin"), Sense::drag()).on_hover_text("Composition marker bin: drag a marker to the time ruler");
let resp = ui.interact(bin, egui::Id::new("marker-bin"), Sense::drag()).on_hover_text(tr("Composition marker bin: drag a marker to the time ruler"));
app.auto.add("timeline.markerBin", bin, "Composition marker bin");
let hot = resp.hovered() || resp.dragged();
if hot {
+9 -8
View File
@@ -3,6 +3,7 @@
//! Both edit the session's paint options through `paint.options` / `paint.brushPreset`, so
//! agents see and set exactly what the panels show.
use crate::i18n::tr;
use effectcraft_engine::effects::paint::{BRUSH_PRESETS, CHANNELS, DURATIONS, ERASE_MODES, MODES};
use egui::{Align2, Color32, Rect, Sense, Stroke, StrokeKind, pos2, vec2};
use serde_json::{Value, json};
@@ -112,7 +113,7 @@ pub fn paint(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
let sresp = ui.interact(swap, egui::Id::new("paint-swap"), Sense::click());
p.text(swap.center(), Align2::CENTER_CENTER, "⇄", Tokens::ui(12.0), if sresp.hovered() { t.text } else { t.text_dim });
app.auto.add("paint.swapColors", swap, "Swap colors");
if sresp.on_hover_text("Swap Paint Colors (X)").clicked() {
if sresp.on_hover_text(tr("Swap Paint Colors (X)")).clicked() {
set(app, json!({"color": o.background, "background": o.color}));
}
let reset = Rect::from_min_size(pos2(x0 + 46.0, y + 18.0), vec2(18.0, 14.0));
@@ -120,7 +121,7 @@ pub fn paint(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
p.rect_filled(Rect::from_min_size(reset.min + vec2(4.0, 3.0), vec2(7.0, 6.0)), 0.0, Color32::WHITE);
p.rect_stroke(Rect::from_min_size(reset.min + vec2(8.0, 6.0), vec2(7.0, 6.0)), 0.0, Stroke::new(1.0, t.text_dim), StrokeKind::Inside);
app.auto.add("paint.resetColors", reset, "Default colors");
if rresp.on_hover_text("Set Paint Colors to Black and White (D)").clicked() {
if rresp.on_hover_text(tr("Set Paint Colors to Black and White (D)")).clicked() {
set(app, json!({"color": [1.0, 1.0, 1.0, 1.0], "background": [0.0, 0.0, 0.0, 1.0]}));
}
y += 40.0;
@@ -168,9 +169,9 @@ pub fn paint(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
// Clone Options.
p.line_segment([pos2(x0, y), pos2(x0 + w, y)], Stroke::new(1.0, t.separator));
y += 8.0;
p.text(pos2(x0, y + 8.0), Align2::LEFT_CENTER, "Clone Options", Tokens::semibold(11.5), if clone { t.text } else { t.text_faint });
p.text(pos2(x0, y + 8.0), Align2::LEFT_CENTER, tr("Clone Options"), Tokens::semibold(11.5), if clone { t.text } else { t.text_faint });
y += 22.0;
p.text(pos2(x0, y + 10.0), Align2::LEFT_CENTER, "Preset:", Tokens::ui(11.5), t.text_dim);
p.text(pos2(x0, y + 10.0), Align2::LEFT_CENTER, tr("Preset:"), Tokens::ui(11.5), t.text_dim);
for i in 1..=5u32 {
let r = Rect::from_min_size(pos2(x0 + 56.0 + (i - 1) as f32 * 26.0, y), vec2(22.0, 20.0));
let resp = ui.interact(r, egui::Id::new(("clone-preset", i)), Sense::click());
@@ -217,7 +218,7 @@ pub fn paint(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
}
y += 26.0;
let off = o.clone_offset.unwrap_or([0.0, 0.0]);
p.text(pos2(x0, y + 9.0), Align2::LEFT_CENTER, "Offset:", Tokens::ui(11.5), t.text_dim);
p.text(pos2(x0, y + 9.0), Align2::LEFT_CENTER, tr("Offset:"), Tokens::ui(11.5), t.text_dim);
for (d, v) in off.iter().enumerate() {
let (r, nv, _) = widgets::hot_number_at(ui, pos2(x0 + 56.0 + d as f32 * 64.0, y), egui::Id::new(("clone-off", d)), *v, 1.0, (-1e5, 1e5), 0, "", &t);
app.auto.add(&format!("paint.cloneOffset.{d}"), r, "Clone offset");
@@ -240,7 +241,7 @@ pub fn paint(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
set(app, json!({"showOverlay": !o.show_overlay}));
}
app.auto.add("paint.showOverlay", r, "Clone Source Overlay");
p.text(pos2(r.max.x + 4.0, r.center().y), Align2::LEFT_CENTER, "Clone Source Overlay:", Tokens::ui(11.5), t.text_dim);
p.text(pos2(r.max.x + 4.0, r.center().y), Align2::LEFT_CENTER, tr("Clone Source Overlay:"), Tokens::ui(11.5), t.text_dim);
if let Some(nv) = number(app, ui, &p, x0 + 150.0, y, "", "paint.overlayOpacity", o.overlay_opacity, (0.0, 100.0), " %", true) {
set(app, json!({"overlayOpacity": nv}));
}
@@ -329,7 +330,7 @@ pub fn brushes(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
}
let r = Rect::from_min_size(pos2(x0, y), vec2(18.0, 20.0));
// Spacing (checkbox in AE turns spacing on; we always space dabs, so it shows the value).
p.text(pos2(x0, y + 9.0), Align2::LEFT_CENTER, "Spacing:", Tokens::ui(11.5), t.text_dim);
p.text(pos2(x0, y + 9.0), Align2::LEFT_CENTER, tr("Spacing:"), Tokens::ui(11.5), t.text_dim);
let _ = r;
let (sr, nv, _) = widgets::hot_number_at(ui, pos2(x0 + 58.0, y), egui::Id::new("brushes-spacing"), o.spacing, 0.5, (1.0, 1000.0), 0, " %", &t);
app.auto.add("brushes.spacing", sr, "Spacing");
@@ -338,7 +339,7 @@ pub fn brushes(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
}
y += 30.0;
// Brush Dynamics.
p.text(pos2(x0, y + 8.0), Align2::LEFT_CENTER, "Brush Dynamics", Tokens::semibold(11.5), t.text);
p.text(pos2(x0, y + 8.0), Align2::LEFT_CENTER, tr("Brush Dynamics"), Tokens::semibold(11.5), t.text);
y += 22.0;
let opts = vec!["Off".to_string(), "Pen Pressure".to_string()];
for (label, key, on) in
+9 -8
View File
@@ -12,6 +12,7 @@
//! `createNulls.tracePath`, `createNulls.loop`, `vrEditor.env.<output id>`, `vrEditor.pan`,
//! `vrEditor.tilt`, `vrEditor.roll`, `vrEditor.reset`, `vrEditor.open`.
use crate::i18n::tr;
use egui::{Rect, RichText};
use serde_json::{Value, json};
@@ -38,7 +39,7 @@ pub fn create_nulls(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
let loop_id = egui::Id::new("createNulls.loop");
let mut looping: bool = ui.data(|d| d.get_temp(loop_id)).unwrap_or(false);
let ready = app.session.is_enabled("paths.nullsFollowPoints") && !app.session.state.selected_layers.is_empty();
ui.label(RichText::new("Select a layer with a mask or shape path (or the path property), or Puppet pins.").color(t.text_dim).small());
ui.label(RichText::new(tr("Select a layer with a mask or shape path (or the path property), or Puppet pins.")).color(t.text_dim).small());
ui.add_space(6.0);
let mut acts = vec![];
for (id, label, tip) in [
@@ -53,7 +54,7 @@ pub fn create_nulls(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
acts.push((id, if id == "paths.tracePath" { json!({"loop": looping}) } else { json!({}) }));
}
}
let r = ui.checkbox(&mut looping, "Loop (Trace Path)");
let r = ui.checkbox(&mut looping, tr("Loop (Trace Path)"));
app.auto.add("createNulls.loop", r.rect, "Loop");
ui.data_mut(|d| d.insert_temp(loop_id, looping));
run(app, &ctx, acts);
@@ -66,7 +67,7 @@ pub fn vr_editor(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
let mut ui = area(ui, rect);
let envs = app.session.execute("comp.vr.environments", json!({})).ok().and_then(|v| v.as_array().cloned()).unwrap_or_default();
if envs.is_empty() {
ui.label(RichText::new("No VR environments yet. Create one with Composition ▸ VR ▸ Create VR Environment…").color(t.text_faint));
ui.label(RichText::new(tr("No VR environments yet. Create one with Composition ▸ VR ▸ Create VR Environment…")).color(t.text_faint));
return;
}
let sel_id = egui::Id::new("vrEditor.selected");
@@ -78,7 +79,7 @@ pub fn vr_editor(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
sel = active.and_then(|c| envs.iter().find(|e| owns(e, c))).or(envs.first()).and_then(|e| e["output"].as_u64()).unwrap_or(0);
}
let mut acts = vec![];
ui.label(RichText::new("VR Environments").strong());
ui.label(RichText::new(tr("VR Environments")).strong());
for e in &envs {
let out = e["output"].as_u64().unwrap_or(0);
let r = ui.selectable_label(out == sel, e["name"].as_str().unwrap_or(""));
@@ -90,7 +91,7 @@ pub fn vr_editor(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
ui.data_mut(|d| d.insert_temp(sel_id, sel));
let Some(env) = envs.iter().find(|e| e["output"] == sel) else { return };
ui.separator();
ui.label(RichText::new("360 View Orientation").strong());
ui.label(RichText::new(tr("360 View Orientation")).strong());
let view: Vec<f64> = env["view"].as_array().map(|a| a.iter().filter_map(Value::as_f64).collect()).unwrap_or_default();
let signed = |a: f64| if a > 180.0 { a - 360.0 } else { a };
let (mut tilt, mut pan, mut roll) =
@@ -108,17 +109,17 @@ pub fn vr_editor(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
acts.push(("comp.vr.setView", json!({"comp": sel, "pan": pan, "tilt": tilt, "roll": roll})));
}
ui.horizontal(|ui| {
let r = ui.button("Reset");
let r = ui.button(tr("Reset"));
app.auto.add("vrEditor.reset", r.rect, "Reset");
if r.clicked() {
acts.push(("comp.vr.setView", json!({"comp": sel, "orientation": [0.0, 0.0, 0.0]})));
}
let r = ui.button("Open Output");
let r = ui.button(tr("Open Output"));
app.auto.add("vrEditor.open", r.rect, "Open Output");
if r.clicked() {
acts.push(("comp.open", json!({"comp": sel})));
}
});
ui.label(RichText::new("All six face cameras turn together; the scene is not changed.").color(t.text_faint).small());
ui.label(RichText::new(tr("All six face cameras turn together; the scene is not changed.")).color(t.text_faint).small());
run(app, &ctx, acts);
}
+10 -10
View File
@@ -579,27 +579,27 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
}
let footage = matches!(it.kind, ItemKind::Footage(_));
resp.context_menu(|ui| {
if ui.button("Delete").clicked() {
if ui.button(crate::i18n::tr("Delete")).clicked() {
actions.push(("project.delete".into(), json!({"items": targets(app, *id, selected)})));
ui.close();
}
if matches!(it.kind, ItemKind::Comp(_)) && ui.button("Open Composition").clicked() {
if matches!(it.kind, ItemKind::Comp(_)) && ui.button(crate::i18n::tr("Open Composition")).clicked() {
actions.push(("comp.open".into(), json!({"comp": id.0})));
ui.close();
}
if ui.button("Rename").clicked() {
if ui.button(crate::i18n::tr("Rename")).clicked() {
ctx.data_mut(|d| d.insert_temp::<Editing>(edit_id(), (id.0, "name".into(), it.name.clone())));
ui.close();
}
if it.parent.is_some() && ui.button("Move to Project Root").clicked() {
if it.parent.is_some() && ui.button(crate::i18n::tr("Move to Project Root")).clicked() {
actions.push(("project.move".into(), json!({"items": [id.0], "folder": null})));
ui.close();
}
if !it.is_folder() && ui.button("Add to Composition").clicked() {
if !it.is_folder() && ui.button(crate::i18n::tr("Add to Composition")).clicked() {
actions.push(("layer.addItem".into(), json!({"item": id.0})));
ui.close();
}
if ui.button("New Comp from Selection").clicked() {
if ui.button(crate::i18n::tr("New Comp from Selection")).clicked() {
if let Some((w, h)) = it.dimensions() {
let d = it.duration().map(|d| d.seconds()).unwrap_or(10.0);
actions.push(("comp.new".into(), json!({"name": format!("{} Comp", it.name), "width": w, "height": h, "duration": d})));
@@ -608,7 +608,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
ui.close();
}
ui.separator();
ui.menu_button("Label", |ui| {
ui.menu_button(crate::i18n::tr("Label"), |ui| {
for l in Label::ALL {
if widgets::label_entry(ui, l, &app.session.prefs.label_name(l), l == it.label, &t).clicked() {
actions.push(("project.setLabel".into(), json!({"items": targets(app, *id, selected), "label": label_index(l)})));
@@ -734,9 +734,9 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
actions.push(("file.cycleBitDepth".into(), json!({})));
}
}
dresp.on_hover_text("Project color depth: click to cycle 8/16/32 bpc, Alt-click for Project Settings");
dresp.on_hover_text(crate::i18n::tr("Project color depth: click to cycle 8/16/32 bpc, Alt-click for Project Settings"));
let tr = Rect::from_min_size(pos2(foot.max.x - 30.0, foot.min.y + 3.0), vec2(22.0, 22.0));
if widgets::icon_button(ui, tr, Icon::Trash, false, &t, egui::Id::new("ptrash")).on_hover_text("Delete selected project items").clicked()
if widgets::icon_button(ui, tr, Icon::Trash, false, &t, egui::Id::new("ptrash")).on_hover_text(crate::i18n::tr("Delete selected project items")).clicked()
&& !app.session.state.project_selection.is_empty()
{
actions.push(("project.delete".into(), json!({})));
@@ -812,7 +812,7 @@ fn label_index(l: Label) -> usize {
/// The column header's context menu: show or hide the optional columns.
fn column_menu(app: &mut EffectcraftApp, resp: &egui::Response) {
resp.context_menu(|ui| {
ui.label(egui::RichText::new("Columns").weak());
ui.label(egui::RichText::new(crate::i18n::tr("Columns")).weak());
for (key, label, _) in COLUMNS {
let mut on = app.ui.project_columns.iter().any(|c| c == key);
if ui.checkbox(&mut on, label).changed() {
+12 -6
View File
@@ -57,7 +57,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
let comp = app.session.active_comp_arc();
let layer = comp.as_ref().and_then(|c| app.session.state.selected_layers.first().and_then(|id| c.layer(*id)).cloned());
let (Some(comp), Some(cid), Some(layer)) = (comp, app.session.active_comp_id(), layer) else {
p.text(rect.center(), Align2::CENTER_CENTER, "Select a layer to see its properties", Tokens::ui(12.0), t.text_faint);
p.text(rect.center(), Align2::CENTER_CENTER, crate::i18n::tr("Select a layer to see its properties"), Tokens::ui(12.0), t.text_faint);
return;
};
let project = app.session.project.clone();
@@ -76,7 +76,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
section_header(app, ui, &p, x0, w, y, "Layer Transform", "properties.transform");
let rr = Rect::from_min_size(pos2(x0 + w - 40.0, y), vec2(40.0, ROW_H));
let resp = ui.interact(rr, egui::Id::new("props-reset"), Sense::click());
p.text(rr.right_center(), Align2::RIGHT_CENTER, "Reset", Tokens::ui(12.0), if resp.hovered() { t.accent_hover } else { t.accent });
p.text(rr.right_center(), Align2::RIGHT_CENTER, crate::i18n::tr("Reset"), Tokens::ui(12.0), if resp.hovered() { t.accent_hover } else { t.accent });
app.auto.add("properties.transform.reset", rr, "Reset Transform");
if resp.clicked() {
for pr in tr.props() {
@@ -112,7 +112,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
let resp = ui.interact(br, egui::Id::new("props-add-animator"), Sense::click());
p.rect_stroke(br, 4.0, Stroke::new(1.0, if resp.hovered() { t.text_dim } else { t.field_border }), egui::StrokeKind::Inside);
icons::paint(&p, Rect::from_center_size(pos2(br.min.x + 14.0, br.center().y), vec2(10.0, 10.0)), Icon::Plus, t.text);
p.text(pos2(br.min.x + 26.0, br.center().y), Align2::LEFT_CENTER, "Add Animator", Tokens::ui(12.0), t.text);
p.text(pos2(br.min.x + 26.0, br.center().y), Align2::LEFT_CENTER, crate::i18n::tr("Add Animator"), Tokens::ui(12.0), t.text);
app.auto.add("properties.addAnimator", br, "Add Animator");
let pop = egui::Id::new("props-add-animator-pop");
if resp.clicked() {
@@ -132,7 +132,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
let pr = Rect::from_min_size(pos2(br.max.x + 8.0, y), vec2(84.0, 24.0));
let presp = ui.interact(pr, egui::Id::new("props-text-presets"), Sense::click());
p.rect_stroke(pr, 4.0, Stroke::new(1.0, if presp.hovered() { t.text_dim } else { t.field_border }), egui::StrokeKind::Inside);
p.text(pr.center(), Align2::CENTER_CENTER, "Presets", Tokens::ui(12.0), t.text);
p.text(pr.center(), Align2::CENTER_CENTER, crate::i18n::tr("Presets"), Tokens::ui(12.0), t.text);
app.auto.add("properties.textPresets", pr, "Text Animation Presets");
let ppop = egui::Id::new("props-text-presets-pop");
if presp.clicked() {
@@ -362,7 +362,13 @@ fn text_section(
p.text(pos2(col2, y + 9.0), Align2::LEFT_CENTER, "A", Tokens::semibold(11.0), t.text_dim);
let r = Rect::from_min_size(pos2(col2 + 26.0, y), vec2(40.0, 18.0));
let resp = ui.interact(r, egui::Id::new("props-leading-auto"), Sense::click());
p.text(pos2(r.min.x + 2.0, r.center().y), Align2::LEFT_CENTER, "Auto", Tokens::ui(12.0), if resp.hovered() { t.accent_hover } else { t.hot_text });
p.text(
pos2(r.min.x + 2.0, r.center().y),
Align2::LEFT_CENTER,
crate::i18n::tr("Auto"),
Tokens::ui(12.0),
if resp.hovered() { t.accent_hover } else { t.hot_text },
);
app.auto.add("properties.text.leading", r, "leading");
if resp.clicked() {
set(actions, json!({"leading": (doc.size * 1.2).round()}));
@@ -480,7 +486,7 @@ fn more_button(app: &mut EffectcraftApp, ui: &mut egui::Ui, p: &egui::Painter, x
let resp = ui.interact(r, egui::Id::new(id), Sense::click());
p.rect_filled(r, 4.0, if resp.hovered() { t.hover } else { t.field_bg });
p.text(pos2(r.min.x + 8.0, r.center().y), Align2::LEFT_CENTER, "···", Tokens::semibold(12.0), t.text);
p.text(pos2(r.min.x + 26.0, r.center().y), Align2::LEFT_CENTER, "More", Tokens::ui(12.0), t.text);
p.text(pos2(r.min.x + 26.0, r.center().y), Align2::LEFT_CENTER, crate::i18n::tr("More"), Tokens::ui(12.0), t.text);
app.auto.add(auto, r, "More");
if resp.clicked() {
app.show_panel(panel);
+10 -9
View File
@@ -2,6 +2,7 @@
//! Character and Paragraph panels show and change the selected text (creating style runs);
//! otherwise they apply to the whole selected text layer.
use crate::i18n::tr;
use effectcraft_engine::keyframe::{BaselineOption, Composer, Direction, FigureStyle, FigureWidth, Justify, Kerning, TextDoc};
use effectcraft_engine::render::EvalCtx;
use egui::{Align2, Rect, Sense, pos2, vec2};
@@ -232,7 +233,7 @@ pub fn character(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
actions.push(json!({"ligatures": !doc.ligatures}));
}
app.auto.add("character.ligatures", lr, "Ligatures");
p.text(pos2(x0 + 20.0, y + 7.0), Align2::LEFT_CENTER, "Ligatures", Tokens::ui(11.5), t.text_dim);
p.text(pos2(x0 + 20.0, y + 7.0), Align2::LEFT_CENTER, tr("Ligatures"), Tokens::ui(11.5), t.text_dim);
// OpenType: a popup with the font's layout features (like the Character panel's OpenType menu).
let or = Rect::from_min_size(pos2(sr.max.x + 6.0, sr.min.y), vec2(78.0, 22.0));
let opop = egui::Id::new("char-opentype-pop");
@@ -264,7 +265,7 @@ pub fn character(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
let axes = effectcraft_engine::text::variable::font_axes(face);
if !axes.is_empty() {
y += 26.0;
p.text(pos2(x0, y + 7.0), Align2::LEFT_CENTER, "Variable Font Axes", Tokens::semibold(11.0), t.text_dim);
p.text(pos2(x0, y + 7.0), Align2::LEFT_CENTER, tr("Variable Font Axes"), Tokens::semibold(11.0), t.text_dim);
for (i, a) in axes.iter().enumerate() {
let (col, row) = (i % 2, i / 2);
let fx = x0 + col as f32 * (w / 2.0);
@@ -287,7 +288,7 @@ pub fn character(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
scroll.end(ui, &mut app.auto, "character.scroll", y + 44.0 - top, &t);
match &target {
None => {
p.text(pos2(rect.center().x, rect.max.y - 20.0), Align2::CENTER_CENTER, "Select a text layer", Tokens::ui(11.0), t.text_faint);
p.text(pos2(rect.center().x, rect.max.y - 20.0), Align2::CENTER_CENTER, tr("Select a text layer"), Tokens::ui(11.0), t.text_faint);
}
Some(tt) if tt.range.is_some() => {
let [a, b] = tt.range.unwrap_or_default();
@@ -351,7 +352,7 @@ fn opentype_popup(app: &mut EffectcraftApp, ui: &mut egui::Ui, id: egui::Id, pos
}
}
ui.separator();
ui.label(egui::RichText::new("Figures").small().color(t.text_dim));
ui.label(egui::RichText::new(tr("Figures")).small().color(t.text_dim));
let figs = [
("default", "Default Figure Style", FigureStyle::Default, FigureWidth::Default),
("tabularLining", "Tabular Lining", FigureStyle::Lining, FigureWidth::Tabular),
@@ -368,7 +369,7 @@ fn opentype_popup(app: &mut EffectcraftApp, ui: &mut egui::Ui, id: egui::Id, pos
}
}
ui.separator();
ui.label(egui::RichText::new("Stylistic Sets").small().color(t.text_dim));
ui.label(egui::RichText::new(tr("Stylistic Sets")).small().color(t.text_dim));
egui::Grid::new(id.with("sets")).spacing(vec2(3.0, 3.0)).show(ui, |ui| {
for n in 1..=20u32 {
let key = format!("ss{n:02}");
@@ -536,11 +537,11 @@ pub fn paragraph(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
actions.push(json!({"hangingPunctuation": !doc.hanging_punctuation}));
}
app.auto.add("paragraph.hangingPunctuation", hr, "Roman Hanging Punctuation");
p.text(pos2(x0 + 20.0, y + 7.0), Align2::LEFT_CENTER, "Roman Hanging Punctuation", Tokens::ui(11.5), t.text_dim);
p.text(pos2(x0 + 20.0, y + 7.0), Align2::LEFT_CENTER, tr("Roman Hanging Punctuation"), Tokens::ui(11.5), t.text_dim);
// (Room for the status line at the bottom.)
scroll.end(ui, &mut app.auto, "paragraph.scroll", y + 44.0 - top, &t);
if !enabled {
p.text(pos2(rect.center().x, rect.max.y - 20.0), Align2::CENTER_CENTER, "Select a text layer", Tokens::ui(11.0), t.text_faint);
p.text(pos2(rect.center().x, rect.max.y - 20.0), Align2::CENTER_CENTER, tr("Select a text layer"), Tokens::ui(11.0), t.text_faint);
}
let Some(tt) = target else { return };
for a in actions {
@@ -601,7 +602,7 @@ pub fn align(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
let p = ui.painter().with_clip_rect(rect);
let x0 = rect.min.x + 10.0;
let mut y = rect.min.y + 10.0;
p.text(pos2(x0, y + 10.0), Align2::LEFT_CENTER, "Align Layers to:", Tokens::ui(11.5), t.text_dim);
p.text(pos2(x0, y + 10.0), Align2::LEFT_CENTER, tr("Align Layers to:"), Tokens::ui(11.5), t.text_dim);
let dr = Rect::from_min_size(pos2(x0 + 100.0, y), vec2(110.0, 20.0));
let to_sel = app.ui.align_to_selection;
let pid = egui::Id::new("align-to-pop");
@@ -619,7 +620,7 @@ pub fn align(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
let mut run: Option<(&str, &str)> = None;
for (row, kind) in ["align", "distribute"].into_iter().enumerate() {
if row == 1 {
p.text(pos2(x0, y + 8.0), Align2::LEFT_CENTER, "Distribute Layers:", Tokens::ui(11.5), t.text_dim);
p.text(pos2(x0, y + 8.0), Align2::LEFT_CENTER, tr("Distribute Layers:"), Tokens::ui(11.5), t.text_dim);
y += 20.0;
}
for (i, op) in ops.iter().enumerate() {
+59 -34
View File
@@ -1499,11 +1499,11 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
}
hresp.context_menu(|ui| {
let under: &str = ctx.data(|d| d.get_temp(egui::Id::new("tl-colhdr-under"))).unwrap_or("");
if !under.is_empty() && under != "name" && ui.button("Hide This").clicked() {
if !under.is_empty() && under != "name" && ui.button(crate::i18n::tr("Hide This")).clicked() {
actions.push(("timeline.column".into(), json!({"column": under, "visible": false})));
ui.close();
}
ui.menu_button("Columns", |ui| {
ui.menu_button(crate::i18n::tr("Columns"), |ui| {
for (id, label) in COLUMNS {
let label = if id == "name" { if app.ui.timeline.source_name { "Source Name" } else { "Layer Name" } } else { label };
let on = column_visible(&app.ui.timeline, id);
@@ -1513,7 +1513,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
}
}
});
if ui.button(if app.ui.timeline.source_name { "Show Layer Name" } else { "Show Source Name" }).clicked() {
if ui.button(if app.ui.timeline.source_name { crate::i18n::tr("Show Layer Name") } else { crate::i18n::tr("Show Source Name") }).clicked() {
actions.push(("timeline.sourceName".into(), json!({})));
ui.close();
}
@@ -1799,8 +1799,9 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
// Parent.
if vis.parent {
let pw_rect = Rect::from_center_size(pos2(cw.parent + 9.0, cy), vec2(15.0, 15.0));
let pw =
ui.interact(pw_rect, egui::Id::new(("parent-whip", layer.id.0)), Sense::drag()).on_hover_text("Parent pick whip: drag onto a layer");
let pw = ui
.interact(pw_rect, egui::Id::new(("parent-whip", layer.id.0)), Sense::drag())
.on_hover_text(crate::i18n::tr("Parent pick whip: drag onto a layer"));
icons::paint(&lp, pw_rect.shrink(1.0), Icon::PickWhip, if pw.hovered() || pw.dragged() { t.text } else { t.text_dim });
app.auto.add(&format!("timeline.layer.{}.pickWhip", layer.id.0), pw_rect, "Parent pick whip");
if pw.drag_started() {
@@ -1870,7 +1871,9 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
if gr.width() < 2.0 {
continue;
}
let gresp = ui.interact(gr, egui::Id::new(("ghost", side, layer.id.0)), Sense::drag()).on_hover_text("Drag to slip the source");
let gresp = ui
.interact(gr, egui::Id::new(("ghost", side, layer.id.0)), Sense::drag())
.on_hover_text(crate::i18n::tr("Drag to slip the source"));
app.auto.add(&format!("timeline.layer.{}.source{}", layer.id.0, side), gr, "Slip");
if gresp.hovered() || gresp.dragged() {
ctx.set_cursor_icon(egui::CursorIcon::ResizeHorizontal);
@@ -1999,9 +2002,10 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
text_anim_popups(app, ui, &lp, layer, *uid, cw.switches, cy, &mut actions);
shape_add_popup(app, ui, &lp, layer, *uid, cw.switches, cy, &mut actions);
if *uid == GEOM_BIT | layer.id.0 {
let lr = lp.text(pos2(cw.switches + 6.0, cy), Align2::LEFT_CENTER, "Change Renderer…", Tokens::ui(11.5), t.accent);
let resp =
ui.interact(lr, egui::Id::new(("tl-change-renderer", layer.id.0)), Sense::click()).on_hover_text("Composition Settings ▸ 3D Renderer");
let lr = lp.text(pos2(cw.switches + 6.0, cy), Align2::LEFT_CENTER, crate::i18n::tr("Change Renderer…"), Tokens::ui(11.5), t.accent);
let resp = ui
.interact(lr, egui::Id::new(("tl-change-renderer", layer.id.0)), Sense::click())
.on_hover_text(crate::i18n::tr("Composition Settings ▸ 3D Renderer"));
if resp.hovered() {
ctx.set_cursor_icon(egui::CursorIcon::PointingHand);
}
@@ -2028,7 +2032,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
if widgets::checkbox(ui, ir, inverted, &t, egui::Id::new(("minv", uid))).clicked() {
actions.push(("layer.setMask".into(), json!({"layer": layer.id.0, "mask": uid, "inverted": !inverted})));
}
lp.text(pos2(ir.max.x + 4.0, cy), Align2::LEFT_CENTER, "Inverted", Tokens::ui(11.0), t.text_dim);
lp.text(pos2(ir.max.x + 4.0, cy), Align2::LEFT_CENTER, crate::i18n::tr("Inverted"), Tokens::ui(11.0), t.text_dim);
}
let gr_rect = Rect::from_min_max(pos2(indent + 8.0, r.min.y), pos2(cw.switches, r.max.y));
let gr = ui.interact(gr_rect, egui::Id::new(("grow", uid)), Sense::click());
@@ -2072,11 +2076,11 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
let Some(ex) = prop.expr.clone() else { continue };
lp.rect_filled(left, 0.0, t.row_alt);
let indent = cw.name + 6.0 + 14.0 * row.depth as f32;
lp.text(pos2(indent + 12.0, cy), Align2::LEFT_CENTER, "Expression:", Tokens::ui(11.5), t.text_dim);
lp.text(pos2(indent + 12.0, cy), Align2::LEFT_CENTER, crate::i18n::tr("Expression:"), Tokens::ui(11.5), t.text_dim);
lp.text(pos2(indent + 82.0, cy), Align2::LEFT_CENTER, &prop.name, Tokens::ui(11.5), t.text);
// "=" enable switch and the property pick whip (AE's expression controls).
let en_r = Rect::from_center_size(pos2(cw.switches + 10.0, cy), vec2(16.0, 16.0));
let en = ui.interact(en_r, egui::Id::new(("expr-en", uid)), Sense::click()).on_hover_text("Enable Expression");
let en = ui.interact(en_r, egui::Id::new(("expr-en", uid)), Sense::click()).on_hover_text(crate::i18n::tr("Enable Expression"));
lp.rect_stroke(en_r.shrink(1.0), 2.0, Stroke::new(1.0, t.separator), StrokeKind::Inside);
let expr_col = Color32::from_rgb(0xe8, 0x7c, 0x5c);
lp.text(
@@ -2091,7 +2095,9 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
actions.push(("prop.setExpression".into(), json!({"layer": layer.id.0, "prop": uid, "enabled": !ex.enabled})));
}
let pw_rect = Rect::from_center_size(pos2(cw.switches + 30.0, cy), vec2(15.0, 15.0));
let pw = ui.interact(pw_rect, egui::Id::new(("expr-whip", uid)), Sense::drag()).on_hover_text("Expression pick whip: drag onto a property");
let pw = ui
.interact(pw_rect, egui::Id::new(("expr-whip", uid)), Sense::drag())
.on_hover_text(crate::i18n::tr("Expression pick whip: drag onto a property"));
icons::paint(&lp, pw_rect.shrink(1.0), Icon::PickWhip, if pw.hovered() || pw.dragged() { t.text } else { t.text_dim });
app.auto.add(&format!("timeline.prop.{uid}.pickWhip"), pw_rect, "Expression pick whip");
if pw.is_pointer_button_down_on() && ui.input(|i| i.pointer.primary_pressed()) {
@@ -2127,7 +2133,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
}
// ⓕ: the Expression Language menu inserts at the cursor.
let lang_r = Rect::from_center_size(pos2(cw.switches + 70.0, cy), vec2(16.0, 16.0));
let lang = ui.interact(lang_r, egui::Id::new(("expr-lang", uid)), Sense::click()).on_hover_text("Expression Language Menu");
let lang = ui.interact(lang_r, egui::Id::new(("expr-lang", uid)), Sense::click()).on_hover_text(crate::i18n::tr("Expression Language Menu"));
lp.circle_stroke(lang_r.center(), 6.5, Stroke::new(1.0, if lang.hovered() { t.text } else { t.text_dim }));
lp.text(lang_r.center(), Align2::CENTER_CENTER, "f", Tokens::semibold(10.0), if lang.hovered() { t.text } else { t.text_dim });
app.auto.add(&format!("timeline.prop.{uid}.exprLanguage"), lang_r, "Expression Language Menu");
@@ -2396,7 +2402,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
] {
if lbl == "Select All Keyframes" {
// Keyframe colour labels.
ui.menu_button("Label", |ui| {
ui.menu_button(crate::i18n::tr("Label"), |ui| {
for (i, l) in effectcraft_engine::color::Label::ALL.into_iter().enumerate() {
if widgets::label_entry(ui, l, &app.session.prefs.label_name(l), k.label as usize == i, &t).clicked() {
if !ks {
@@ -2459,7 +2465,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
}
for (response, area) in [(&outline_empty, "outline"), (&empty, "graph")] {
response.context_menu(|ui| {
let menu = ui.menu_button("New", |ui| {
let menu = ui.menu_button(crate::i18n::tr("New"), |ui| {
for (key, label, command, params) in [
("text", "Text", "layer.newText", json!({"text": "Text"})),
("solid", "Solid…", "layer.newSolid", json!({})),
@@ -2478,7 +2484,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
}
});
app.auto.add(&format!("timeline.context.{area}.new"), menu.response.rect, "New");
let response = ui.button("Composition Settings…");
let response = ui.button(crate::i18n::tr("Composition Settings…"));
app.auto.add(&format!("timeline.context.{area}.settings"), response.rect, "Composition Settings…");
if response.clicked() {
actions.push(("comp.settings".into(), json!({})));
@@ -2697,13 +2703,25 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
}
let tgl = Rect::from_min_size(pos2(footer.min.x + 10.0, footer.min.y + 3.0), vec2(150.0, 18.0));
let tresp = ui.interact(tgl, egui::Id::new("tl-toggle-modes"), Sense::click());
p.text(tgl.left_center(), Align2::LEFT_CENTER, "Toggle Switches / Modes", Tokens::ui(11.0), if tresp.hovered() { t.text } else { t.text_dim });
p.text(
tgl.left_center(),
Align2::LEFT_CENTER,
crate::i18n::tr("Toggle Switches / Modes"),
Tokens::ui(11.0),
if tresp.hovered() { t.text } else { t.text_dim },
);
app.auto.add("timeline.toggleSwitchesModes", tgl, "Toggle Switches / Modes");
if tresp.clicked() {
app.ui.timeline.show_modes = !app.ui.timeline.show_modes;
}
let ms = app.frames.last_ms.lock().map(|v| *v).unwrap_or(0.0);
p.text(pos2(graph_x0 - 12.0, footer.center().y), Align2::RIGHT_CENTER, format!("Frame Render Time {ms:.0}ms"), Tokens::ui(11.0), t.text_faint);
p.text(
pos2(graph_x0 - 12.0, footer.center().y),
Align2::RIGHT_CENTER,
crate::i18n::tr_args("Frame Render Time {}ms", &[&format!("{ms:.0}")]),
Tokens::ui(11.0),
t.text_faint,
);
// Zoom slider.
let zs = Rect::from_min_max(pos2(graph_x0 + 30.0, footer.center().y - 2.0), pos2((graph_x0 + 230.0).min(rect.max.x - 30.0), footer.center().y + 2.0));
icons::paint(&p, Rect::from_center_size(pos2(zs.min.x - 14.0, footer.center().y), vec2(14.0, 14.0)), Icon::MountainSmall, t.text_dim);
@@ -2891,7 +2909,7 @@ fn layer_cells(
ctx.data_mut(|d| d.insert_temp(edit_id, (el, buf)));
}
} else {
let resp = ui.interact(cr, egui::Id::new(("tl-comment", lid)), Sense::click()).on_hover_text("Double-click to edit the comment");
let resp = ui.interact(cr, egui::Id::new(("tl-comment", lid)), Sense::click()).on_hover_text(crate::i18n::tr("Double-click to edit the comment"));
lp.with_clip_rect(cr.intersect(lp.clip_rect())).text(pos2(cr.min.x + 2.0, cy), Align2::LEFT_CENTER, &layer.comment, Tokens::ui(11.5), t.text_dim);
if resp.double_clicked() {
ctx.data_mut(|d| d.insert_temp(edit_id, (lid, layer.comment.clone())));
@@ -2910,7 +2928,7 @@ fn layer_cells(
}
let cr = Rect::from_min_max(pos2(x + 2.0, r.min.y + 1.0), pos2(x + TIME_W - 2.0, r.max.y - 1.0));
let tc = crate::panels::timecode(&app.session, comp, Tick::from_seconds_f64(secs) - if kind == "duration" { comp.display_start } else { Tick(0) });
let resp = ui.interact(cr, egui::Id::new(("tl-cell", kind, lid)), Sense::drag()).on_hover_text("Drag to change");
let resp = ui.interact(cr, egui::Id::new(("tl-cell", kind, lid)), Sense::drag()).on_hover_text(crate::i18n::tr("Drag to change"));
lp.text(pos2(cr.min.x + 4.0, cy), Align2::LEFT_CENTER, &tc, Tokens::mono(11.0), if resp.hovered() || resp.dragged() { t.hot_text } else { t.timecode });
app.auto.add(&format!("timeline.layer.{lid}.{kind}"), cr, &tc);
if resp.hovered() || resp.dragged() {
@@ -2941,7 +2959,7 @@ fn layer_cells(
if cw.vis.stretch {
let cr = Rect::from_min_max(pos2(cw.stretch + 2.0, r.min.y + 1.0), pos2(cw.stretch + STRETCH_W - 2.0, r.max.y - 1.0));
let label = format!("{:.1}%", layer.stretch * 100.0);
let resp = ui.interact(cr, egui::Id::new(("tl-stretch", lid)), Sense::click()).on_hover_text("Time Stretch…");
let resp = ui.interact(cr, egui::Id::new(("tl-stretch", lid)), Sense::click()).on_hover_text(crate::i18n::tr("Time Stretch…"));
lp.text(pos2(cr.min.x + 4.0, cy), Align2::LEFT_CENTER, &label, Tokens::ui(11.5), if resp.hovered() { t.hot_text } else { t.text });
app.auto.add(&format!("timeline.layer.{lid}.stretch"), cr, &label);
if resp.clicked() {
@@ -3041,9 +3059,10 @@ fn shape_add_popup(
return;
}
let t = app.tokens;
p.text(pos2(x + 6.0, cy), Align2::LEFT_CENTER, "Add:", Tokens::ui(11.5), t.text_dim);
p.text(pos2(x + 6.0, cy), Align2::LEFT_CENTER, crate::i18n::tr("Add:"), Tokens::ui(11.5), t.text_dim);
let br = Rect::from_center_size(pos2(x + 40.0, cy), vec2(16.0, 16.0));
let resp = ui.interact(br, egui::Id::new(("tl-shapeadd", uid)), Sense::click()).on_hover_text("Add a shape, fill, stroke or path operation");
let resp =
ui.interact(br, egui::Id::new(("tl-shapeadd", uid)), Sense::click()).on_hover_text(crate::i18n::tr("Add a shape, fill, stroke or path operation"));
icons::paint(p, br.shrink(3.0), Icon::ChevronRight, if resp.hovered() { t.text } else { t.text_dim });
app.auto.add(&format!("timeline.group.{uid}.add"), br, "Add:");
let pop = egui::Id::new(("tl-shapeadd-pop", uid));
@@ -3174,7 +3193,7 @@ pub(crate) fn layer_menu(ui: &mut egui::Ui, layers: &[u64], actions: &mut Vec<(S
ui.close();
}
};
ui.menu_button("New", |ui| {
ui.menu_button(crate::i18n::tr("New"), |ui| {
item(ui, "Text", "layer.newText", json!({"text": "Text"}));
item(ui, "Solid…", "app.solidSettings", json!({}));
item(ui, "Light…", "layer.newLight", json!({}));
@@ -3185,24 +3204,24 @@ pub(crate) fn layer_menu(ui: &mut egui::Ui, layers: &[u64], actions: &mut Vec<(S
});
item(ui, "Layer Settings…", "layer.settings", json!({}));
ui.separator();
ui.menu_button("Masks", |ui| {
ui.menu_button(crate::i18n::tr("Masks"), |ui| {
item(ui, "New Mask", "layer.addMask", json!({"layer": id}));
});
ui.menu_button("Transform", |ui| {
ui.menu_button(crate::i18n::tr("Transform"), |ui| {
item(ui, "Reset", "layer.transform", json!({"layers": layers, "op": "reset"}));
item(ui, "Center In View", "layer.transform", json!({"layers": layers, "op": "center"}));
item(ui, "Fit to Comp", "layer.transform", json!({"layers": layers, "op": "fit"}));
item(ui, "Flip Horizontal", "layer.transform", json!({"layers": layers, "op": "flipH"}));
item(ui, "Flip Vertical", "layer.transform", json!({"layers": layers, "op": "flipV"}));
});
ui.menu_button("Time", |ui| {
ui.menu_button(crate::i18n::tr("Time"), |ui| {
item(ui, "Enable Time Remapping", "layer.enableTimeRemap", json!({"layers": layers}));
item(ui, "Time-Reverse Layer", "layer.timeReverse", json!({"layers": layers}));
item(ui, "Time Stretch…", "layer.timeStretch", json!({}));
item(ui, "Freeze Frame", "layer.freezeFrame", json!({"layers": layers}));
item(ui, "Freeze on Last Frame", "layer.freezeOnLastFrame", json!({"layers": layers}));
});
ui.menu_button("Blending Mode", |ui| {
ui.menu_button(crate::i18n::tr("Blending Mode"), |ui| {
widgets::menu_scroll(ui, |ui| {
for m in BlendMode::ALL {
item(ui, m.label(), "layer.setBlendMode", json!({"layers": layers, "mode": m.label()}));
@@ -3212,7 +3231,7 @@ pub(crate) fn layer_menu(ui: &mut egui::Ui, layers: &[u64], actions: &mut Vec<(S
}
})
});
ui.menu_button("Arrange", |ui| {
ui.menu_button(crate::i18n::tr("Arrange"), |ui| {
item(ui, "Bring Layer to Front", "layer.arrange", json!({"layers": layers, "to": "front"}));
item(ui, "Bring Layer Forward", "layer.arrange", json!({"layers": layers, "to": "forward"}));
item(ui, "Send Layer Backward", "layer.arrange", json!({"layers": layers, "to": "backward"}));
@@ -3304,7 +3323,7 @@ fn value_editor(
let linked = linkable && !app.ui.timeline.unlinked.contains(&uid);
if linkable {
let lr = Rect::from_center_size(pos2(x + 7.0, at.y), vec2(14.0, 14.0));
let resp = ui.interact(lr, egui::Id::new(("tl-link", uid)), Sense::click()).on_hover_text("Constrain Proportions");
let resp = ui.interact(lr, egui::Id::new(("tl-link", uid)), Sense::click()).on_hover_text(crate::i18n::tr("Constrain Proportions"));
icons::paint(p, lr, Icon::Link, if linked { t.accent } else { t.text_faint });
app.auto.add(&format!("timeline.prop.{uid}.link"), lr, "Constrain Proportions");
if resp.clicked() && !app.ui.timeline.unlinked.remove(&uid) {
@@ -3379,14 +3398,20 @@ fn value_editor(
}
}
Value::Path(_) => {
p.text(pos2(x, at.y), Align2::LEFT_CENTER, "Shape…", Tokens::ui(12.0), t.hot_text);
p.text(pos2(x, at.y), Align2::LEFT_CENTER, crate::i18n::tr("Shape…"), Tokens::ui(12.0), t.hot_text);
}
Value::Text(_) => {}
Value::Layer(l) => {
p.text(pos2(x, at.y), Align2::LEFT_CENTER, l.map(|l| format!("Layer {l}")).unwrap_or("None".into()), Tokens::ui(12.0), t.hot_text);
p.text(
pos2(x, at.y),
Align2::LEFT_CENTER,
l.map(|l| crate::i18n::tr_args("Layer {}", &[&l])).unwrap_or(crate::i18n::tr("None").into()),
Tokens::ui(12.0),
t.hot_text,
);
}
Value::Gradient(_) => {
p.text(pos2(x, at.y), Align2::LEFT_CENTER, "Edit Gradient…", Tokens::ui(12.0), t.hot_text);
p.text(pos2(x, at.y), Align2::LEFT_CENTER, crate::i18n::tr("Edit Gradient…"), Tokens::ui(12.0), t.hot_text);
}
Value::Str(s) => {
p.text(pos2(x, at.y), Align2::LEFT_CENTER, s.chars().take(30).collect::<String>(), Tokens::ui(12.0), t.text_dim);
+16 -16
View File
@@ -153,7 +153,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
}
y += 34.0;
let Some(comp) = app.session.active_comp_arc() else {
p.text(pos2(x0, y + 8.0), Align2::LEFT_CENTER, "Open a composition to track motion.", Tokens::ui(12.0), t.text_faint);
p.text(pos2(x0, y + 8.0), Align2::LEFT_CENTER, crate::i18n::tr("Open a composition to track motion."), Tokens::ui(12.0), t.text_faint);
return;
};
// A selected mask switches the panel to mask tracking.
@@ -476,10 +476,10 @@ fn ok_cancel(ui: &mut egui::Ui, app: &mut EffectcraftApp, t: &Tokens, prefix: &s
let (mut ok, mut cancel) = (false, false);
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(crate::i18n::tr(" OK ")).color(Color32::WHITE)).fill(t.accent));
app.auto.add(&format!("{prefix}.ok"), r.rect, "OK");
ok = r.clicked();
let r = ui.button("Cancel");
let r = ui.button(crate::i18n::tr("Cancel"));
app.auto.add(&format!("{prefix}.cancel"), r.rect, "Cancel");
cancel = r.clicked();
});
@@ -491,12 +491,12 @@ pub fn options_dialog(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens)
let (mut ok, mut cancel) = (false, false);
super::dialogs::modal(ctx, "Motion Tracker Options", vec2(460.0, 470.0), t, |ui| {
ui.horizontal(|ui| {
ui.label("Track Name:");
ui.label(crate::i18n::tr("Track Name:"));
let r = ui.text_edit_singleline(&mut d.name);
app.auto.add("dialog.trackOptions.name", r.rect, "Track Name");
});
ui.add_space(8.0);
ui.label(egui::RichText::new("Channel").font(Tokens::medium(12.5)));
ui.label(egui::RichText::new(crate::i18n::tr("Channel")).font(Tokens::medium(12.5)));
ui.horizontal(|ui| {
for (c, l) in [(TrackChannel::Rgb, "RGB"), (TrackChannel::Luminance, "Luminance"), (TrackChannel::Saturation, "Saturation")] {
let r = ui.radio_value(&mut d.opts.channel, c, l);
@@ -504,21 +504,21 @@ pub fn options_dialog(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens)
}
});
ui.add_space(8.0);
ui.label(egui::RichText::new("Process Before Match").font(Tokens::medium(12.5)));
ui.label(egui::RichText::new(crate::i18n::tr("Process Before Match")).font(Tokens::medium(12.5)));
ui.horizontal(|ui| {
let r = ui.checkbox(&mut d.blur_on, "Blur");
let r = ui.checkbox(&mut d.blur_on, crate::i18n::tr("Blur"));
app.auto.add("dialog.trackOptions.blurOn", r.rect, "Blur");
let r = ui.add_enabled(d.blur_on, egui::DragValue::new(&mut d.opts.blur).speed(0.1).range(0.5..=50.0).suffix(" pixels"));
app.auto.add("dialog.trackOptions.blur", r.rect, "Blur pixels");
let r = ui.checkbox(&mut d.opts.enhance, "Enhance");
let r = ui.checkbox(&mut d.opts.enhance, crate::i18n::tr("Enhance"));
app.auto.add("dialog.trackOptions.enhance", r.rect, "Enhance");
});
ui.add_space(8.0);
let r = ui.checkbox(&mut d.opts.subpixel, "Subpixel Positioning");
let r = ui.checkbox(&mut d.opts.subpixel, crate::i18n::tr("Subpixel Positioning"));
app.auto.add("dialog.trackOptions.subpixel", r.rect, "Subpixel Positioning");
let r = ui.checkbox(&mut d.opts.adapt_every_frame, "Adapt Feature On Every Frame");
let r = ui.checkbox(&mut d.opts.adapt_every_frame, crate::i18n::tr("Adapt Feature On Every Frame"));
app.auto.add("dialog.trackOptions.adaptEveryFrame", r.rect, "Adapt Feature On Every Frame");
let r = ui.checkbox(&mut d.opts.track_shape, "Follow Feature Rotation and Scale");
let r = ui.checkbox(&mut d.opts.track_shape, crate::i18n::tr("Follow Feature Rotation and Scale"));
app.auto.add("dialog.trackOptions.trackShape", r.rect, "Follow Feature Rotation and Scale");
ui.add_space(8.0);
ui.horizontal(|ui| {
@@ -538,7 +538,7 @@ pub fn options_dialog(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens)
})
.response;
app.auto.add("dialog.trackOptions.action", r.rect, "If Confidence is Below action");
ui.label("If Confidence is Below");
ui.label(crate::i18n::tr("If Confidence is Below"));
let r = ui.add(egui::DragValue::new(&mut d.opts.threshold).speed(0.5).range(0.0..=100.0).suffix(" %"));
app.auto.add("dialog.trackOptions.threshold", r.rect, "Confidence threshold");
});
@@ -578,15 +578,15 @@ pub fn target_dialog(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens)
let layers: Vec<(u64, String)> =
app.session.active_comp().map(|c| c.layers.iter().enumerate().map(|(i, l)| (l.id.0, format!("{}. {}", i + 1, l.name))).collect()).unwrap_or_default();
super::dialogs::modal(ctx, "Motion Target", vec2(400.0, 200.0), t, |ui| {
ui.label("Apply Motion To:");
ui.label(crate::i18n::tr("Apply Motion To:"));
ui.horizontal(|ui| {
ui.label("Layer:");
ui.label(crate::i18n::tr("Layer:"));
let cur = d.target.and_then(|id| layers.iter().find(|(l, _)| *l == id)).map(|(_, n)| n.clone()).unwrap_or_else(|| "None".into());
let r = egui::ComboBox::from_id_salt("track-target")
.selected_text(cur)
.width(240.0)
.show_ui(ui, |ui| {
ui.selectable_value(&mut d.target, None, "None");
ui.selectable_value(&mut d.target, None, crate::i18n::tr("None"));
for (id, n) in &layers {
ui.selectable_value(&mut d.target, Some(*id), n);
}
@@ -613,7 +613,7 @@ pub fn apply_dialog(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
let (mut ok, mut cancel) = (false, false);
super::dialogs::modal(ctx, "Motion Tracker Apply Options", vec2(380.0, 170.0), t, |ui| {
ui.horizontal(|ui| {
ui.label("Apply Dimensions:");
ui.label(crate::i18n::tr("Apply Dimensions:"));
let labels = ["X and Y", "X only", "Y only"];
let r = egui::ComboBox::from_id_salt("track-apply-dims")
.selected_text(labels[d.dims.min(2)])
+7 -7
View File
@@ -454,7 +454,7 @@ fn aux_views(
p.rect_stroke(Rect::from_center_size(cr.center(), cr.size() * k), 0.0, Stroke::new(1.0, Color32::from_white_alpha(120)), StrokeKind::Middle);
}
}
let label = if comp.has_3d() { v.label() } else { "Active Camera" };
let label = if comp.has_3d() { v.label() } else { crate::i18n::tr("Active Camera") };
p.text(r.left_bottom() + vec2(8.0, -8.0), Align2::LEFT_BOTTOM, label, Tokens::ui(11.0), Color32::from_white_alpha(200));
app.auto.add(&format!("viewer.view.{}", v.id()), r, label);
}
@@ -511,7 +511,7 @@ fn locked_pane(app: &mut EffectcraftApp, ui: &mut egui::Ui, full: Rect, bg: Colo
p.rect_filled(cr, 0.0, Color32::from_rgb((b[0] * 255.0) as u8, (b[1] * 255.0) as u8, (b[2] * 255.0) as u8));
p.image(tex.id(), cr, Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)), Color32::WHITE);
p.rect_stroke(cr, 0.0, Stroke::new(1.0, Color32::from_black_alpha(160)), StrokeKind::Outside);
let label = format!("Locked: {name} \u{2014} {}", if comp.has_3d() { lv.view.label() } else { "Active Camera" });
let label = crate::i18n::tr_args("Locked: {} — {}", &[&name, &(if comp.has_3d() { lv.view.label() } else { crate::i18n::tr("Active Camera") })]);
p.text(r.left_top() + vec2(8.0, 8.0), Align2::LEFT_TOP, &label, Tokens::ui(11.0), Color32::from_white_alpha(210));
app.auto.add("viewer.locked", r, &label);
// Close the locked viewer.
@@ -974,7 +974,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
resp.context_menu(|ui| {
let hits = ui.data(|d| d.get_temp::<Vec<(u64, String)>>(menu_hits_id)).unwrap_or_default();
if !hits.is_empty() {
ui.menu_button("Select", |ui| {
ui.menu_button(crate::i18n::tr("Select"), |ui| {
crate::widgets::menu_scroll(ui, |ui| {
for (id, name) in &hits {
if ui.button(name).clicked() {
@@ -987,7 +987,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
ui.separator();
}
if context_layers.is_empty() {
ui.label("No unlocked layer selected");
ui.label(crate::i18n::tr("No unlocked layer selected"));
} else {
super::timeline::layer_menu(ui, &context_layers, &mut context_actions);
}
@@ -1392,7 +1392,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
let speed = app.session.state.puppet.record_speed.max(1.0) / 100.0;
app.session.set_time(cti + Tick::from_seconds_f64(now / speed));
painter.circle_filled(pos, 6.0, Color32::from_rgb(0xe0, 0x30, 0x30));
painter.text(pos + vec2(10.0, -10.0), Align2::LEFT_BOTTOM, "Recording", Tokens::ui(11.0), Color32::from_rgb(0xff, 0x60, 0x60));
painter.text(pos + vec2(10.0, -10.0), Align2::LEFT_BOTTOM, crate::i18n::tr("Recording"), Tokens::ui(11.0), Color32::from_rgb(0xff, 0x60, 0x60));
ui.data_mut(|dd| dd.insert_temp(gid, Gesture::PuppetRecord { layer, pin, others, inv, began, cti, samples }));
ui.ctx().request_repaint();
}
@@ -1633,7 +1633,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
if app.playback.playing && app.playback.waiting {
let r = Rect::from_min_size(area.min + vec2(10.0, 10.0), vec2(150.0, 22.0));
painter.rect_filled(r, 11.0, Color32::from_black_alpha(170));
painter.text(r.center(), Align2::CENTER_CENTER, "Caching frames…", Tokens::ui(11.5), t.cache_green);
painter.text(r.center(), Align2::CENTER_CENTER, crate::i18n::tr("Caching frames…"), Tokens::ui(11.5), t.cache_green);
}
vt::draw_snap(&ctx, &painter, &map, &ectx);
@@ -1917,7 +1917,7 @@ fn empty_state(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
let p = ui.painter();
p.rect_filled(rect, 0.0, t.pasteboard);
let c = rect.center();
p.text(c - vec2(0.0, 40.0), Align2::CENTER_CENTER, "Composition", Tokens::semibold(16.0), t.text);
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() {
crate::panels::dialogs::open_new_comp(app);
+12 -8
View File
@@ -225,8 +225,9 @@ pub(crate) fn rulers(app: &mut EffectcraftApp, ui: &mut egui::Ui, map: &ViewerMa
// Zero point: drag the corner, double-click to reset.
p.line_segment([corner.center() - vec2(4.0, 0.0), corner.center() + vec2(4.0, 0.0)], tick);
p.line_segment([corner.center() - vec2(0.0, 4.0), corner.center() + vec2(0.0, 4.0)], tick);
let cr =
ui.interact(corner, egui::Id::new("vw-ruler-origin"), Sense::click_and_drag()).on_hover_text("Ruler zero point (drag to move, double-click to reset)");
let cr = ui
.interact(corner, egui::Id::new("vw-ruler-origin"), Sense::click_and_drag())
.on_hover_text(crate::i18n::tr("Ruler zero point (drag to move, double-click to reset)"));
app.auto.add("viewer.ruler.origin", corner, "Ruler zero point");
let fg = ui.ctx().layer_painter(egui::LayerId::new(egui::Order::Foreground, egui::Id::new("vw-ruler-fg")));
if cr.dragged()
@@ -247,7 +248,7 @@ pub(crate) fn rulers(app: &mut EffectcraftApp, ui: &mut egui::Ui, map: &ViewerMa
// Guides dragged out of the rulers: the top ruler makes horizontal guides, the left one
// vertical guides.
for (r, vertical, id) in [(top, false, "top"), (left, true, "left")] {
let resp = ui.interact(r, egui::Id::new(("vw-ruler", id)), Sense::drag()).on_hover_text("Drag to create a guide");
let resp = ui.interact(r, egui::Id::new(("vw-ruler", id)), Sense::drag()).on_hover_text(crate::i18n::tr("Drag to create a guide"));
app.auto.add(&format!("viewer.ruler.{id}"), r, if vertical { "Vertical ruler" } else { "Horizontal ruler" });
if resp.hovered() {
ui.ctx().set_cursor_icon(if vertical { egui::CursorIcon::ResizeHorizontal } else { egui::CursorIcon::ResizeVertical });
@@ -356,7 +357,7 @@ pub(crate) fn draw_frame(app: &mut EffectcraftApp, ctx: &egui::Context, painter:
let snap = showing_snapshot(app, ctx);
// Settings ▸ Video ▸ Mirror on Computer Monitor off: playback goes to Video Preview only.
if crate::prefs_live::main_viewer_hidden(app) && !snap {
painter.text(comp_rect.center(), Align2::CENTER_CENTER, "Playing on Video Preview", Tokens::ui(13.0), Color32::from_gray(150));
painter.text(comp_rect.center(), Align2::CENTER_CENTER, crate::i18n::tr("Playing on Video Preview"), Tokens::ui(13.0), Color32::from_gray(150));
return;
}
if opts.fast_previews == FastPreviews::Wireframe && !snap {
@@ -403,7 +404,7 @@ pub(crate) fn draw_frame(app: &mut EffectcraftApp, ctx: &egui::Context, painter:
}
};
painter.image(tex.id(), comp_rect, uv, Color32::WHITE);
painter.text(comp_rect.left_top() + vec2(6.0, 6.0), Align2::LEFT_TOP, "Snapshot", Tokens::ui(11.0), SNAP_COLOR);
painter.text(comp_rect.left_top() + vec2(6.0, 6.0), Align2::LEFT_TOP, crate::i18n::tr("Snapshot"), Tokens::ui(11.0), SNAP_COLOR);
return;
}
// A GPU frame (the web viewer): drawn straight from its texture; Show Channel / exposure
@@ -530,7 +531,10 @@ pub(crate) fn bottom_bar(app: &mut EffectcraftApp, ui: &mut egui::Ui, bar: Rect,
// Magnification.
let r = Rect::from_min_size(pos2(x, cy - 10.0), vec2(80.0, 20.0));
if widgets::dropdown(ui, r, &magnification_label(zoom * ppp * 100.0), &t, egui::Id::new("vw-mag")).on_hover_text("Magnification ratio popup").clicked() {
if widgets::dropdown(ui, r, &magnification_label(zoom * ppp * 100.0), &t, egui::Id::new("vw-mag"))
.on_hover_text(crate::i18n::tr("Magnification ratio popup"))
.clicked()
{
toggle_popup(ui, "vw-mag-pop");
}
app.auto.add("viewer.magnification", r, "Magnification");
@@ -560,7 +564,7 @@ pub(crate) fn bottom_bar(app: &mut EffectcraftApp, ui: &mut egui::Ui, bar: Rect,
let r = Rect::from_min_size(pos2(x, cy - 10.0), vec2(84.0, 20.0));
let scale = app.viewer_scale(zoom, ppp);
if widgets::dropdown(ui, r, &resolution_label(app.ui.viewer.res, scale), &t, egui::Id::new("vw-res"))
.on_hover_text("Resolution/Down Sample Factor Popup")
.on_hover_text(crate::i18n::tr("Resolution/Down Sample Factor Popup"))
.clicked()
{
toggle_popup(ui, "vw-res-pop");
@@ -666,7 +670,7 @@ pub(crate) fn bottom_bar(app: &mut EffectcraftApp, ui: &mut egui::Ui, bar: Rect,
.fixed_decimals(1)
.custom_formatter(|v, _| if v == 0.0 { "0.0".into() } else { format!("{v:+.1}") }),
);
let resp = resp.on_hover_text("Adjust Exposure (stops)");
let resp = resp.on_hover_text(crate::i18n::tr("Adjust Exposure (stops)"));
app.auto.add("viewer.exposure", er, "Adjust Exposure");
if resp.changed() {
let _ = app.session.execute("view.exposure", json!({"stops": ev}));
+3 -2
View File
@@ -6,6 +6,7 @@
//! the active viewer is locked shows it in an unlocked viewer, or in a new one if every viewer is
//! locked.
use crate::i18n::tr;
use std::sync::Arc;
use effectcraft_engine::project::ItemId;
@@ -88,7 +89,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, id: u32, rect: Rect) {
let ctx = ui.ctx().clone();
let p = ui.painter().with_clip_rect(rect);
p.rect_filled(rect, 0.0, t.pasteboard);
let resp = ui.interact(rect, egui::Id::new(("passive-viewer", id)), Sense::click()).on_hover_text("Click to make this viewer active");
let resp = ui.interact(rect, egui::Id::new(("passive-viewer", id)), Sense::click()).on_hover_text(tr("Click to make this viewer active"));
app.auto.add(&format!("viewers.{id}"), rect, "Composition viewer (click to make it active)");
match comp_of(app, id).and_then(|cid| Some((cid, app.session.project.comp(cid)?.clone()))) {
Some((cid, c)) => {
@@ -116,7 +117,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, id: u32, rect: Rect) {
p.rect_stroke(r, 0.0, egui::Stroke::new(1.0, t.separator), egui::StrokeKind::Outside);
}
None => {
p.text(rect.center(), Align2::CENTER_CENTER, "Click to show the active composition here", Tokens::ui(12.0), t.text_faint);
p.text(rect.center(), Align2::CENTER_CENTER, tr("Click to show the active composition here"), Tokens::ui(12.0), t.text_faint);
}
}
if resp.clicked() {