feat(engine): clip move, clip effect_input, mandatory static FFmpeg

- oakengine_sequence_move_clip implemented for real (oaktimeline
  TrackMoveBlockCommand; fixes the graph-ownership/gap-anchor/ripple
  trim bugs the stub was hiding); same-track via the frozen C ABI,
  cross-track supported by the module command
- oaknode clip blocks now declare a tex_in texture input and set
  effect_input to it, so timeline clips can host effect chains; facade
  test covers effect insert/remove on a real clip
- oakffmpeg-link: FFMPEG_DIR is now mandatory with a clear panic (a
  Homebrew upgrade left the system ffmpeg .pc pointing at a deleted
  dav1d Cellar path, breaking links); reads a git-ignored workspace
  .env for IDEs that cannot inject env vars (RustRover); links the C++
  stdlib for C++ codec libs (svt-av1)
- oakengine re-exports oaknode so tests share one crate instance;
  it_node uses the direct instance's value type where it calls the
  module FFI (the --workspace dev-dependency feature split builds
  oaknode twice)
This commit is contained in:
2026-08-11 23:04:48 +08:00
parent a209f63d52
commit 18ff60f147
351 changed files with 32880 additions and 15939 deletions
+47 -17
View File
@@ -84,10 +84,7 @@ impl TextGeneratorV1 {
.get(TEXT_INPUT)
.map(to_text)
.unwrap_or_else(|| String::new());
let html = matches!(
row.get(HTML_INPUT),
Some(NodeValue::Boolean(true))
);
let html = matches!(row.get(HTML_INPUT), Some(NodeValue::Boolean(true)));
let mut mode = TextLayoutMode::PlainText;
let text = if html {
// QTextDocument::setHtml() doesn't translate newlines, so they
@@ -103,7 +100,10 @@ impl TextGeneratorV1 {
text,
mode,
font_family: row.get(FONT_INPUT).map(to_text).unwrap_or_else(String::new),
font_size_pt: row.get(FONT_SIZE_INPUT).map(NodeValue::to_double).unwrap_or(0.0),
font_size_pt: row
.get(FONT_SIZE_INPUT)
.map(NodeValue::to_double)
.unwrap_or(0.0),
dots_per_meter: 0,
wrap_width: (tenth_of_width * 8) as f64,
center_horizontally: true,
@@ -115,7 +115,12 @@ impl TextGeneratorV1 {
/// valign combo (top: 10% top margin; center: frame center; bottom:
/// 10% bottom margin). The C++ math is integer (`width()/10`,
/// `height()/2 - doc_height/2`, ...), mirrored here.
pub fn draw_offsets(valign: i32, frame_width: i32, frame_height: i32, doc_height: i32) -> (f64, f64) {
pub fn draw_offsets(
valign: i32,
frame_width: i32,
frame_height: i32,
doc_height: i32,
) -> (f64, f64) {
let tenth_of_width = frame_width / 10;
let offset_x = tenth_of_width as f64;
let offset_y = match valign {
@@ -205,11 +210,18 @@ impl NodeBehavior for TextGeneratorV1 {
/// null texture handle marking a renderer-deferred generate job
/// resolved via [`NodeBehavior::generate_frame`]
/// (`// CPP-PARITY: textv1.cpp` `value()`).
fn value(&self, core: &NodeCore, inputs: &NodeValueRow, time: Rational, table: &mut NodeValueTable) {
let text = inputs
.get(TEXT_INPUT)
.map(to_text)
.unwrap_or_else(|| core.value_at_time(TEXT_INPUT, -1, time).to_double().to_string());
fn value(
&self,
core: &NodeCore,
inputs: &NodeValueRow,
time: Rational,
table: &mut NodeValueTable,
) {
let text = inputs.get(TEXT_INPUT).map(to_text).unwrap_or_else(|| {
core.value_at_time(TEXT_INPUT, -1, time)
.to_double()
.to_string()
});
if !text.is_empty() {
table.push(
crate::value::ValueType::Texture,
@@ -325,19 +337,34 @@ mod tests {
#[test]
fn create_wires_inputs() {
let (core, behavior) = create();
assert_eq!(behavior.type_id(), "org.olivevideoeditor.Olive.textgenerator");
assert_eq!(core.get_input(TEXT_INPUT).unwrap().value_type, ValueType::Text);
assert_eq!(
behavior.type_id(),
"org.olivevideoeditor.Olive.textgenerator"
);
assert_eq!(
core.get_input(TEXT_INPUT).unwrap().value_type,
ValueType::Text
);
assert_eq!(
core.get_input(TEXT_INPUT).unwrap().default,
NodeValue::Text("Sample Text".to_string())
);
assert_eq!(core.get_input(HTML_INPUT).unwrap().value_type, ValueType::Boolean);
assert_eq!(
core.get_input(HTML_INPUT).unwrap().value_type,
ValueType::Boolean
);
assert_eq!(
core.get_input(COLOR_INPUT).unwrap().default,
NodeValue::Color([1.0, 1.0, 1.0, 1.0])
);
assert_eq!(core.get_input(V_ALIGN_INPUT).unwrap().default, NodeValue::Combo(1));
assert_eq!(core.get_input(FONT_SIZE_INPUT).unwrap().default, NodeValue::Float(72.0));
assert_eq!(
core.get_input(V_ALIGN_INPUT).unwrap().default,
NodeValue::Combo(1)
);
assert_eq!(
core.get_input(FONT_SIZE_INPUT).unwrap().default,
NodeValue::Float(72.0)
);
assert_ne!(core.flags & crate::node::flags::DONT_SHOW_IN_CREATE_MENU, 0);
}
@@ -361,7 +388,10 @@ mod tests {
#[test]
fn layout_request_html_translates_newlines() {
let mut row = NodeValueRow::default();
row.insert(TEXT_INPUT.to_string(), NodeValue::Text("line1\nline2".to_string()));
row.insert(
TEXT_INPUT.to_string(),
NodeValue::Text("line1\nline2".to_string()),
);
row.insert(HTML_INPUT.to_string(), NodeValue::Boolean(true));
let req = TextGeneratorV1::layout_request(&row, 1280);
assert_eq!(req.text, "line1<br>line2");