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:
+94
-11
@@ -138,6 +138,10 @@ pub struct TransitionBlockBehavior {
|
||||
|
||||
/// ClipBlock input ids (C++ `clip.cpp`).
|
||||
pub mod clip_input {
|
||||
/// `tex_in` (texture, static) — the clip's effect input (C++
|
||||
/// `set_effect_input`; the Rust clip keeps the `tex_in` naming used by
|
||||
/// every effect node while C++ master names the buffer `buffer_in`).
|
||||
pub const TEXTURE_INPUT: &str = "tex_in";
|
||||
/// `media_in_in` (rational, static).
|
||||
pub const MEDIA_IN: &str = "media_in_in";
|
||||
/// `speed_in` (float, static).
|
||||
@@ -288,9 +292,27 @@ impl NodeBehavior for TransitionBlockBehavior {
|
||||
|
||||
/// Constructor for a clip block (C++ `ClipBlock::ClipBlock()`): adds the
|
||||
/// static clip inputs (`media_in_in`, `speed_in`, `reverse_in`,
|
||||
/// `maintain_audio_pitch_in`, `autocache_in`, `loop_in`).
|
||||
/// `maintain_audio_pitch_in`, `autocache_in`, `loop_in`) and the texture
|
||||
/// input (`tex_in`, prepended ahead of the static ones), which doubles as
|
||||
/// the clip's effect input.
|
||||
pub fn clip_create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
let mut core = NodeCore::new();
|
||||
|
||||
// The texture input (C++ `ClipBlock` prepends it ahead of the static
|
||||
// inputs): this is where the effect chain attaches, so it sits right
|
||||
// after the inherited `enabled_in` and stays connectable. An unconnected
|
||||
// `tex_in` is inert — the traverser only feeds rows from actual edges
|
||||
// and `ClipBlockBehavior` never reads inputs, so a bare clip (no
|
||||
// effects) evaluates exactly as before.
|
||||
let mut tex = Input::new(
|
||||
clip_input::TEXTURE_INPUT,
|
||||
ValueType::Texture,
|
||||
NodeValue::None,
|
||||
);
|
||||
tex.flags |= crate::input::flags::NOT_KEYFRAMABLE;
|
||||
core.inputs.insert(1, tex);
|
||||
core.effect_input = clip_input::TEXTURE_INPUT.to_string();
|
||||
|
||||
let mut media_in = Input::new(
|
||||
clip_input::MEDIA_IN,
|
||||
ValueType::Rational,
|
||||
@@ -299,11 +321,7 @@ pub fn clip_create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
media_in.flags |= crate::input::flags::NOT_CONNECTABLE | crate::input::flags::NOT_KEYFRAMABLE;
|
||||
core.add_input(media_in);
|
||||
|
||||
let mut speed = Input::new(
|
||||
clip_input::SPEED,
|
||||
ValueType::Float,
|
||||
NodeValue::Float(1.0),
|
||||
);
|
||||
let mut speed = Input::new(clip_input::SPEED, ValueType::Float, NodeValue::Float(1.0));
|
||||
speed.flags |= crate::input::flags::NOT_CONNECTABLE | crate::input::flags::NOT_KEYFRAMABLE;
|
||||
speed.properties = vec![
|
||||
("min".to_string(), NodeValue::Float(0.0)),
|
||||
@@ -327,11 +345,7 @@ pub fn clip_create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
pitch.flags |= crate::input::flags::NOT_CONNECTABLE | crate::input::flags::NOT_KEYFRAMABLE;
|
||||
core.add_input(pitch);
|
||||
|
||||
let mut loop_mode = Input::new(
|
||||
clip_input::LOOP_MODE,
|
||||
ValueType::Combo,
|
||||
NodeValue::Combo(0),
|
||||
);
|
||||
let mut loop_mode = Input::new(clip_input::LOOP_MODE, ValueType::Combo, NodeValue::Combo(0));
|
||||
loop_mode.flags |= crate::input::flags::NOT_CONNECTABLE | crate::input::flags::NOT_KEYFRAMABLE;
|
||||
loop_mode.properties = vec![(
|
||||
"combobox_strings".to_string(),
|
||||
@@ -372,3 +386,72 @@ pub fn transition_create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
|
||||
(core, Box::new(TransitionBlockBehavior::new()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::project::Project;
|
||||
|
||||
/// The clip's `tex_in` is declared as a connectable, non-keyframable
|
||||
/// texture input and is the node's effect input (C++ ClipBlock
|
||||
/// prepends the texture input and sets it as the effect input).
|
||||
#[test]
|
||||
fn clip_effect_input_and_texture_input() {
|
||||
let (core, _) = clip_create();
|
||||
assert_eq!(core.effect_input, clip_input::TEXTURE_INPUT);
|
||||
|
||||
let tex = core
|
||||
.get_input(clip_input::TEXTURE_INPUT)
|
||||
.expect("clip declares a texture input");
|
||||
assert_eq!(tex.value_type, ValueType::Texture);
|
||||
assert_eq!(tex.default, NodeValue::None);
|
||||
assert_ne!(tex.flags & crate::input::flags::NOT_KEYFRAMABLE, 0);
|
||||
assert!(
|
||||
tex.is_connectable(),
|
||||
"effects attach through the texture input"
|
||||
);
|
||||
|
||||
// The texture input sits right after the inherited `enabled_in`,
|
||||
// ahead of the static clip inputs (C++ prepend convention).
|
||||
let ids: Vec<&str> = core.inputs.iter().map(|i| i.id.as_str()).collect();
|
||||
assert_eq!(
|
||||
ids,
|
||||
vec![
|
||||
crate::node::ENABLED_INPUT,
|
||||
clip_input::TEXTURE_INPUT,
|
||||
clip_input::MEDIA_IN,
|
||||
clip_input::SPEED,
|
||||
clip_input::REVERSE,
|
||||
clip_input::MAINTAIN_AUDIO_PITCH,
|
||||
clip_input::LOOP_MODE,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// An effect node can be chained onto the clip through `tex_in`: the
|
||||
/// connection succeeds and resolves back to the effect.
|
||||
#[test]
|
||||
fn clip_texture_input_accepts_effects() {
|
||||
let project = Project::new();
|
||||
let (clip_id, effect_id) = {
|
||||
let mut p = project.lock().unwrap();
|
||||
let (ccore, cbehavior) = clip_create();
|
||||
let clip = p.graph.add_node(ccore, cbehavior);
|
||||
let (ecore, ebehavior) = (crate::factory::Factory::global()
|
||||
.find("org.olivevideoeditor.Olive.opacity")
|
||||
.unwrap()
|
||||
.create)();
|
||||
let effect = p.graph.add_node(ecore, ebehavior);
|
||||
p.graph
|
||||
.connect(effect, clip, clip_input::TEXTURE_INPUT, -1)
|
||||
.unwrap();
|
||||
(clip, effect)
|
||||
};
|
||||
let p = project.lock().unwrap();
|
||||
assert_eq!(
|
||||
p.graph
|
||||
.connected_output(clip_id, clip_input::TEXTURE_INPUT, -1),
|
||||
Some(effect_id)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,14 +50,7 @@ pub fn xml_reader_next_start_element(reader: CHandle) -> Option<bool> {
|
||||
/// `oakcommon_xml_reader_name` (two-stage).
|
||||
pub fn xml_reader_name(reader: CHandle) -> Option<String> {
|
||||
two_stage_string("oakcommon_xml_reader_name", |buf, size| unsafe {
|
||||
Some(oakcommon::ffi::xmlutils::oakcommon_xml_reader_name(reader.clone(), buf, size))
|
||||
})
|
||||
}
|
||||
|
||||
/// `oakcommon_xml_reader_read_element_text` (two-stage).
|
||||
pub fn xml_reader_read_element_text(reader: CHandle) -> Option<String> {
|
||||
two_stage_string("oakcommon_xml_reader_read_element_text", |buf, size| unsafe {
|
||||
Some(oakcommon::ffi::xmlutils::oakcommon_xml_reader_read_element_text(
|
||||
Some(oakcommon::ffi::xmlutils::oakcommon_xml_reader_name(
|
||||
reader.clone(),
|
||||
buf,
|
||||
size,
|
||||
@@ -65,6 +58,22 @@ pub fn xml_reader_read_element_text(reader: CHandle) -> Option<String> {
|
||||
})
|
||||
}
|
||||
|
||||
/// `oakcommon_xml_reader_read_element_text` (two-stage).
|
||||
pub fn xml_reader_read_element_text(reader: CHandle) -> Option<String> {
|
||||
two_stage_string(
|
||||
"oakcommon_xml_reader_read_element_text",
|
||||
|buf, size| unsafe {
|
||||
Some(
|
||||
oakcommon::ffi::xmlutils::oakcommon_xml_reader_read_element_text(
|
||||
reader.clone(),
|
||||
buf,
|
||||
size,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// `oakcommon_xml_reader_skip_current_element`.
|
||||
pub fn xml_reader_skip_current_element(reader: CHandle) -> Option<c_int> {
|
||||
Some(unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_skip_current_element(reader) })
|
||||
@@ -80,24 +89,28 @@ pub fn xml_reader_attribute_count(reader: CHandle) -> Option<c_int> {
|
||||
/// `oakcommon_xml_reader_attribute_name` (two-stage).
|
||||
pub fn xml_reader_attribute_name(reader: CHandle, index: c_int) -> Option<String> {
|
||||
two_stage_string("oakcommon_xml_reader_attribute_name", |buf, size| unsafe {
|
||||
Some(oakcommon::ffi::xmlutils::oakcommon_xml_reader_attribute_name(
|
||||
reader.clone(),
|
||||
index,
|
||||
buf,
|
||||
size,
|
||||
))
|
||||
Some(
|
||||
oakcommon::ffi::xmlutils::oakcommon_xml_reader_attribute_name(
|
||||
reader.clone(),
|
||||
index,
|
||||
buf,
|
||||
size,
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// `oakcommon_xml_reader_attribute_value` (two-stage).
|
||||
pub fn xml_reader_attribute_value(reader: CHandle, index: c_int) -> Option<String> {
|
||||
two_stage_string("oakcommon_xml_reader_attribute_value", |buf, size| unsafe {
|
||||
Some(oakcommon::ffi::xmlutils::oakcommon_xml_reader_attribute_value(
|
||||
reader.clone(),
|
||||
index,
|
||||
buf,
|
||||
size,
|
||||
))
|
||||
Some(
|
||||
oakcommon::ffi::xmlutils::oakcommon_xml_reader_attribute_value(
|
||||
reader.clone(),
|
||||
index,
|
||||
buf,
|
||||
size,
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -133,7 +146,11 @@ pub fn xml_writer_attribute(writer: CHandle, name: &str, value: &str) -> Option<
|
||||
let n = CString::new(name).ok()?;
|
||||
let v = CString::new(value).ok()?;
|
||||
Some(unsafe {
|
||||
oakcommon::ffi::xmlutils::oakcommon_xml_writer_write_attribute(writer, n.as_ptr(), v.as_ptr())
|
||||
oakcommon::ffi::xmlutils::oakcommon_xml_writer_write_attribute(
|
||||
writer,
|
||||
n.as_ptr(),
|
||||
v.as_ptr(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -152,7 +169,11 @@ pub fn xml_writer_text_element(writer: CHandle, name: &str, text: &str) -> Optio
|
||||
let n = CString::new(name).ok()?;
|
||||
let t = CString::new(text).ok()?;
|
||||
Some(unsafe {
|
||||
oakcommon::ffi::xmlutils::oakcommon_xml_writer_write_text_element(writer, n.as_ptr(), t.as_ptr())
|
||||
oakcommon::ffi::xmlutils::oakcommon_xml_writer_write_text_element(
|
||||
writer,
|
||||
n.as_ptr(),
|
||||
t.as_ptr(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -169,7 +190,11 @@ pub fn xml_writer_end_document(writer: CHandle) -> Option<c_int> {
|
||||
/// `oakcommon_xml_writer_output` (two-stage).
|
||||
pub fn xml_writer_output(writer: CHandle) -> Option<String> {
|
||||
two_stage_string("oakcommon_xml_writer_output", |buf, size| unsafe {
|
||||
Some(oakcommon::ffi::xmlutils::oakcommon_xml_writer_output(writer.clone(), buf, size))
|
||||
Some(oakcommon::ffi::xmlutils::oakcommon_xml_writer_output(
|
||||
writer.clone(),
|
||||
buf,
|
||||
size,
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -200,7 +225,14 @@ pub fn videoparams_init_basic(
|
||||
) -> Option<CHandle> {
|
||||
Some(unsafe {
|
||||
oakcommon::ffi::videoparams::oakcommon_videoparams_init_basic(
|
||||
width, height, pixel_format, channels, par_num, par_den, interlacing, divider,
|
||||
width,
|
||||
height,
|
||||
pixel_format,
|
||||
channels,
|
||||
par_num,
|
||||
par_den,
|
||||
interlacing,
|
||||
divider,
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -267,7 +299,11 @@ pub fn videoparams_get_frame_rate(params: CHandle) -> Option<(c_int, c_int)> {
|
||||
let mut n = 0;
|
||||
let mut d = 0;
|
||||
let rc = unsafe {
|
||||
oakcommon::ffi::videoparams::oakcommon_videoparams_get_frame_rate(params.clone(), &mut n, &mut d)
|
||||
oakcommon::ffi::videoparams::oakcommon_videoparams_get_frame_rate(
|
||||
params.clone(),
|
||||
&mut n,
|
||||
&mut d,
|
||||
)
|
||||
};
|
||||
Some(if rc < 0 { (0, 0) } else { (n, d) })
|
||||
}
|
||||
@@ -295,7 +331,9 @@ pub fn colortransform_init_display(display: &str, view: &str, look: &str) -> Opt
|
||||
pub fn colortransform_init_output(output: &str) -> Option<CHandle> {
|
||||
use std::ffi::CString;
|
||||
let o = CString::new(output).ok()?;
|
||||
Some(unsafe { oakcommon::ffi::colortransform::oakcommon_colortransform_init_output(o.as_ptr()) })
|
||||
Some(unsafe {
|
||||
oakcommon::ffi::colortransform::oakcommon_colortransform_init_output(o.as_ptr())
|
||||
})
|
||||
}
|
||||
|
||||
/// `oakcommon_colortransform_free` — releases the handle locally.
|
||||
@@ -320,50 +358,61 @@ pub fn colortransform_is_display(transform: CHandle) -> Option<bool> {
|
||||
/// `oakcommon_colortransform_get_display` (two-stage).
|
||||
pub fn colortransform_get_display(transform: CHandle) -> Option<String> {
|
||||
two_stage_string("oakcommon_colortransform_get_display", |buf, size| unsafe {
|
||||
Some(oakcommon::ffi::colortransform::oakcommon_colortransform_get_display(
|
||||
transform.clone(),
|
||||
buf,
|
||||
size,
|
||||
))
|
||||
Some(
|
||||
oakcommon::ffi::colortransform::oakcommon_colortransform_get_display(
|
||||
transform.clone(),
|
||||
buf,
|
||||
size,
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// `oakcommon_colortransform_get_output` (two-stage).
|
||||
pub fn colortransform_get_output(transform: CHandle) -> Option<String> {
|
||||
two_stage_string("oakcommon_colortransform_get_output", |buf, size| unsafe {
|
||||
Some(oakcommon::ffi::colortransform::oakcommon_colortransform_get_output(
|
||||
transform.clone(),
|
||||
buf,
|
||||
size,
|
||||
))
|
||||
Some(
|
||||
oakcommon::ffi::colortransform::oakcommon_colortransform_get_output(
|
||||
transform.clone(),
|
||||
buf,
|
||||
size,
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// `oakcommon_colortransform_get_view` (two-stage).
|
||||
pub fn colortransform_get_view(transform: CHandle) -> Option<String> {
|
||||
two_stage_string("oakcommon_colortransform_get_view", |buf, size| unsafe {
|
||||
Some(oakcommon::ffi::colortransform::oakcommon_colortransform_get_view(
|
||||
transform.clone(),
|
||||
buf,
|
||||
size,
|
||||
))
|
||||
Some(
|
||||
oakcommon::ffi::colortransform::oakcommon_colortransform_get_view(
|
||||
transform.clone(),
|
||||
buf,
|
||||
size,
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// `oakcommon_colortransform_get_look` (two-stage).
|
||||
pub fn colortransform_get_look(transform: CHandle) -> Option<String> {
|
||||
two_stage_string("oakcommon_colortransform_get_look", |buf, size| unsafe {
|
||||
Some(oakcommon::ffi::colortransform::oakcommon_colortransform_get_look(
|
||||
transform.clone(),
|
||||
buf,
|
||||
size,
|
||||
))
|
||||
Some(
|
||||
oakcommon::ffi::colortransform::oakcommon_colortransform_get_look(
|
||||
transform.clone(),
|
||||
buf,
|
||||
size,
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Shared two-stage string fetch: query the required size, then read
|
||||
/// into an owned buffer. `None` when the query returns an error.
|
||||
fn two_stage_string<F: Fn(*mut c_char, c_int) -> Option<c_int>>(_sym: &str, call: F) -> Option<String> {
|
||||
fn two_stage_string<F: Fn(*mut c_char, c_int) -> Option<c_int>>(
|
||||
_sym: &str,
|
||||
call: F,
|
||||
) -> Option<String> {
|
||||
let needed = call(std::ptr::null_mut(), 0)?;
|
||||
if needed <= 0 {
|
||||
return Some(String::new());
|
||||
|
||||
@@ -23,7 +23,11 @@ use std::ffi::{c_int, c_void};
|
||||
#[cfg(not(feature = "test-stubs"))]
|
||||
/// `oakcore_audioparams_create` — new owned params (release with
|
||||
/// [`audioparams_free`]).
|
||||
pub fn audioparams_create(sample_rate: c_int, channel_layout: u64, format: c_int) -> Option<*mut c_void> {
|
||||
pub fn audioparams_create(
|
||||
sample_rate: c_int,
|
||||
channel_layout: u64,
|
||||
format: c_int,
|
||||
) -> Option<*mut c_void> {
|
||||
use crate::bridge::dlsym;
|
||||
type F = unsafe extern "C" fn(c_int, u64, c_int) -> *mut c_void;
|
||||
dlsym::call::<F, *mut c_void>("oakcore_audioparams_create", |f| unsafe {
|
||||
@@ -33,7 +37,11 @@ pub fn audioparams_create(sample_rate: c_int, channel_layout: u64, format: c_int
|
||||
|
||||
/// Test-stub path.
|
||||
#[cfg(feature = "test-stubs")]
|
||||
pub fn audioparams_create(sample_rate: c_int, channel_layout: u64, format: c_int) -> Option<*mut c_void> {
|
||||
pub fn audioparams_create(
|
||||
sample_rate: c_int,
|
||||
channel_layout: u64,
|
||||
format: c_int,
|
||||
) -> Option<*mut c_void> {
|
||||
Some(unsafe { stub::oakcore_audioparams_create(sample_rate, channel_layout, format) })
|
||||
}
|
||||
|
||||
@@ -73,7 +81,9 @@ pub fn audioparams_sample_rate(params: *const c_void) -> Option<c_int> {
|
||||
pub fn audioparams_channel_layout(params: *const c_void) -> Option<u64> {
|
||||
use crate::bridge::dlsym;
|
||||
type F = unsafe extern "C" fn(*const c_void) -> u64;
|
||||
dlsym::call::<F, u64>("oakcore_audioparams_channel_layout", |f| unsafe { f(params) })
|
||||
dlsym::call::<F, u64>("oakcore_audioparams_channel_layout", |f| unsafe {
|
||||
f(params)
|
||||
})
|
||||
}
|
||||
|
||||
/// Test-stub path.
|
||||
|
||||
@@ -47,7 +47,9 @@ pub mod cache_kind {
|
||||
pub fn cache_create_for_node(parent: CHandle, kind: i32) -> Option<CHandle> {
|
||||
use crate::bridge::dlsym;
|
||||
type F = unsafe extern "C" fn(CHandle, i32) -> CHandle;
|
||||
dlsym::call::<F, CHandle>("oakrender_cache_create_for_node", |f| unsafe { f(parent, kind) })
|
||||
dlsym::call::<F, CHandle>("oakrender_cache_create_for_node", |f| unsafe {
|
||||
f(parent, kind)
|
||||
})
|
||||
}
|
||||
|
||||
/// `oakrender_cache_free`.
|
||||
@@ -131,9 +133,7 @@ pub fn disk_cache_path() -> Option<String> {
|
||||
pub fn color_config_create_default() -> Option<Result<(), ()>> {
|
||||
use crate::bridge::dlsym;
|
||||
type F = unsafe extern "C" fn() -> i32;
|
||||
let rc = dlsym::call::<F, i32>("oakrender_color_config_create_default", |f| unsafe {
|
||||
f()
|
||||
})?;
|
||||
let rc = dlsym::call::<F, i32>("oakrender_color_config_create_default", |f| unsafe { f() })?;
|
||||
if rc == 0 {
|
||||
Some(Ok(()))
|
||||
} else {
|
||||
|
||||
@@ -30,9 +30,7 @@ pub fn marker_list_create() -> Option<CHandle> {
|
||||
pub fn marker_list_free(list: *mut CHandle) {
|
||||
use crate::bridge::dlsym;
|
||||
type F = unsafe extern "C" fn(*mut CHandle);
|
||||
if let Some(f) = dlsym::call::<F, ()>("oaktimeline_marker_list_free", |f| unsafe {
|
||||
f(list)
|
||||
}) {
|
||||
if let Some(f) = dlsym::call::<F, ()>("oaktimeline_marker_list_free", |f| unsafe { f(list) }) {
|
||||
let _ = f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,7 +202,11 @@ pub fn command_free(command: *mut CHandle) {
|
||||
}
|
||||
|
||||
/// `oakundo_stack_push` (facade-owned stack).
|
||||
pub fn stack_push(stack: CHandle, command: CHandle, text: *const std::ffi::c_char) -> Option<c_int> {
|
||||
pub fn stack_push(
|
||||
stack: CHandle,
|
||||
command: CHandle,
|
||||
text: *const std::ffi::c_char,
|
||||
) -> Option<c_int> {
|
||||
// Direct call into the oakundo crate (single-lib unification).
|
||||
Some(unsafe { oakundo::ffi::undostack::oakundo_undostack_push(stack, command, text) })
|
||||
}
|
||||
@@ -327,7 +331,10 @@ pub(crate) mod stub {
|
||||
let state = unsafe { &mut (*boxed).value };
|
||||
match &mut state.0 {
|
||||
StubCommand::Callback {
|
||||
done, redo, userdata, ..
|
||||
done,
|
||||
redo,
|
||||
userdata,
|
||||
..
|
||||
} => {
|
||||
if !done.swap(true, Ordering::AcqRel) {
|
||||
if let Some(f) = redo {
|
||||
@@ -357,7 +364,10 @@ pub(crate) mod stub {
|
||||
let state = unsafe { &mut (*boxed).value };
|
||||
match &mut state.0 {
|
||||
StubCommand::Callback {
|
||||
done, undo, userdata, ..
|
||||
done,
|
||||
undo,
|
||||
userdata,
|
||||
..
|
||||
} => {
|
||||
if done.swap(false, Ordering::AcqRel) {
|
||||
if let Some(f) = undo {
|
||||
@@ -396,4 +406,3 @@ pub(crate) mod stub {
|
||||
struct SendStub(StubCommand);
|
||||
unsafe impl Send for SendStub {}
|
||||
}
|
||||
|
||||
|
||||
+400
-199
File diff suppressed because it is too large
Load Diff
+27
-15
@@ -219,7 +219,13 @@ impl Graph {
|
||||
/// input is already connected, `from == to`, or the edge would create
|
||||
/// a cycle (the Rust design rejects cycles at connect time so the
|
||||
/// arena never contains them — `// CPP-PARITY: node.cpp:210`).
|
||||
pub fn connect(&mut self, from: NodeId, to: NodeId, input: &str, element: i32) -> crate::error::Result<()> {
|
||||
pub fn connect(
|
||||
&mut self,
|
||||
from: NodeId,
|
||||
to: NodeId,
|
||||
input: &str,
|
||||
element: i32,
|
||||
) -> crate::error::Result<()> {
|
||||
use crate::error::Error;
|
||||
if !self.is_valid(from) || !self.is_valid(to) {
|
||||
return Err(Error::NotFound);
|
||||
@@ -230,10 +236,7 @@ impl Graph {
|
||||
// NOT_FOUND > INVALID(not connectable) > STATE).
|
||||
let input_flags = {
|
||||
let entry = self.get(to).expect("validated above");
|
||||
let input = entry
|
||||
.core
|
||||
.get_input(input)
|
||||
.ok_or(Error::NotFound)?;
|
||||
let input = entry.core.get_input(input).ok_or(Error::NotFound)?;
|
||||
input.flags
|
||||
};
|
||||
if input_flags & crate::input::flags::NOT_CONNECTABLE != 0 {
|
||||
@@ -399,12 +402,7 @@ impl Graph {
|
||||
if !seen.insert(n) {
|
||||
continue;
|
||||
}
|
||||
stack.extend(
|
||||
self.edges
|
||||
.iter()
|
||||
.filter(|e| e.from == n)
|
||||
.map(|e| e.to),
|
||||
);
|
||||
stack.extend(self.edges.iter().filter(|e| e.from == n).map(|e| e.to));
|
||||
}
|
||||
false
|
||||
}
|
||||
@@ -430,7 +428,9 @@ impl Graph {
|
||||
ready.remove(&n);
|
||||
order.push(n);
|
||||
for e in self.edges.iter().filter(|e| e.from == n) {
|
||||
let d = indegree.get_mut(&e.to).expect("every edge endpoint is counted");
|
||||
let d = indegree
|
||||
.get_mut(&e.to)
|
||||
.expect("every edge endpoint is counted");
|
||||
*d -= 1;
|
||||
if *d == 0 {
|
||||
ready.insert(e.to);
|
||||
@@ -451,7 +451,9 @@ impl Graph {
|
||||
/// Intern an input id string, returning its stable key.
|
||||
fn intern_input(&mut self, input: &str) -> u64 {
|
||||
let key = hash_str(input);
|
||||
self.input_names.entry(key).or_insert_with(|| input.to_string());
|
||||
self.input_names
|
||||
.entry(key)
|
||||
.or_insert_with(|| input.to_string());
|
||||
key
|
||||
}
|
||||
|
||||
@@ -511,7 +513,12 @@ impl Graph {
|
||||
|
||||
/// Insert an element into an array input, shifting per-element values,
|
||||
/// keyframes and edges (C++ `Node::input_array_insert`).
|
||||
pub fn input_array_insert(&mut self, id: NodeId, input: &str, index: i32) -> crate::error::Result<()> {
|
||||
pub fn input_array_insert(
|
||||
&mut self,
|
||||
id: NodeId,
|
||||
input: &str,
|
||||
index: i32,
|
||||
) -> crate::error::Result<()> {
|
||||
use crate::error::Error;
|
||||
let entry = self.get_mut(id).ok_or(Error::NotFound)?;
|
||||
let input_ = entry.core.get_input(input).ok_or(Error::NotFound)?;
|
||||
@@ -545,7 +552,12 @@ impl Graph {
|
||||
|
||||
/// Remove an array element, shifting per-element values, keyframes
|
||||
/// and edges up (C++ `Node::input_array_remove`).
|
||||
pub fn input_array_remove(&mut self, id: NodeId, input: &str, index: i32) -> crate::error::Result<()> {
|
||||
pub fn input_array_remove(
|
||||
&mut self,
|
||||
id: NodeId,
|
||||
input: &str,
|
||||
index: i32,
|
||||
) -> crate::error::Result<()> {
|
||||
use crate::error::Error;
|
||||
let entry = self.get_mut(id).ok_or(Error::NotFound)?;
|
||||
let input_ = entry.core.get_input(input).ok_or(Error::NotFound)?;
|
||||
|
||||
@@ -179,10 +179,10 @@ fn interpolate(before: &Keyframe, after: &Keyframe, time: Rational) -> NodeValue
|
||||
let before_val = before.value.to_double();
|
||||
let after_val = after.value.to_double();
|
||||
|
||||
let both_bezier =
|
||||
before.interpolation == Interpolation::Bezier && after.interpolation == Interpolation::Bezier;
|
||||
let one_bezier =
|
||||
before.interpolation == Interpolation::Bezier || after.interpolation == Interpolation::Bezier;
|
||||
let both_bezier = before.interpolation == Interpolation::Bezier
|
||||
&& after.interpolation == Interpolation::Bezier;
|
||||
let one_bezier = before.interpolation == Interpolation::Bezier
|
||||
|| after.interpolation == Interpolation::Bezier;
|
||||
|
||||
if !both_bezier && !one_bezier {
|
||||
// Both linear.
|
||||
|
||||
+88
-22
@@ -246,7 +246,8 @@ impl NodeCore {
|
||||
self.move_element_value(id, e + 1, e);
|
||||
self.move_element_keyframes(id, e + 1, e);
|
||||
}
|
||||
self.standard_values.remove(&(id.to_string(), (size - 1) as i32));
|
||||
self.standard_values
|
||||
.remove(&(id.to_string(), (size - 1) as i32));
|
||||
self.remove_element_keyframes(id, size - 1);
|
||||
if let Some(input) = self.get_input_mut(id) {
|
||||
input.array_size = input.array_size.saturating_sub(1);
|
||||
@@ -303,7 +304,8 @@ impl NodeCore {
|
||||
{
|
||||
return &mut self.keyframes[i].2;
|
||||
}
|
||||
self.keyframes.push((id.to_string(), element, KeyframeTrack::default()));
|
||||
self.keyframes
|
||||
.push((id.to_string(), element, KeyframeTrack::default()));
|
||||
let last = self.keyframes.len() - 1;
|
||||
&mut self.keyframes[last].2
|
||||
}
|
||||
@@ -319,19 +321,20 @@ impl NodeCore {
|
||||
}
|
||||
|
||||
/// Set the standard value of (input, element) (C++ `set_standard_value`).
|
||||
pub fn set_standard_value(
|
||||
&mut self,
|
||||
id: &str,
|
||||
element: i32,
|
||||
value: crate::value::NodeValue,
|
||||
) {
|
||||
self.standard_values.insert((id.to_string(), element), value);
|
||||
pub fn set_standard_value(&mut self, id: &str, element: i32, value: crate::value::NodeValue) {
|
||||
self.standard_values
|
||||
.insert((id.to_string(), element), value);
|
||||
}
|
||||
|
||||
/// Value of `input` at `time`: keyframes when the (input, element)
|
||||
/// track is non-empty, else the standard value (C++
|
||||
/// `get_value_at_time`; `// CPP-PARITY: node.cpp:465`).
|
||||
pub fn value_at_time(&self, id: &str, element: i32, time: oakcore_rs::Rational) -> crate::value::NodeValue {
|
||||
pub fn value_at_time(
|
||||
&self,
|
||||
id: &str,
|
||||
element: i32,
|
||||
time: oakcore_rs::Rational,
|
||||
) -> crate::value::NodeValue {
|
||||
match self.keyframe_track(id, element) {
|
||||
Some(track) if !track.keys().is_empty() => track
|
||||
.value_at(time)
|
||||
@@ -353,13 +356,22 @@ impl NodeCore {
|
||||
/// `Node::is_input_static`): neither connected nor keyframed.
|
||||
/// `inputs` is the render-time input row — a connected input appears
|
||||
/// in the row under its id.
|
||||
pub fn is_input_static(&self, inputs: &crate::value::NodeValueRow, id: &str, element: i32) -> bool {
|
||||
pub fn is_input_static(
|
||||
&self,
|
||||
inputs: &crate::value::NodeValueRow,
|
||||
id: &str,
|
||||
element: i32,
|
||||
) -> bool {
|
||||
!inputs.contains_key(id) && !self.is_input_keyframing(id, element)
|
||||
}
|
||||
|
||||
/// Set the value hint for (input, element) (C++ `set_value_hint_for_input`).
|
||||
pub fn set_value_hint(&mut self, id: &str, element: i32, hint: ValueHint) {
|
||||
if let Some(slot) = self.hints.iter_mut().find(|((i, e), _)| i == id && *e == element) {
|
||||
if let Some(slot) = self
|
||||
.hints
|
||||
.iter_mut()
|
||||
.find(|((i, e), _)| i == id && *e == element)
|
||||
{
|
||||
slot.1 = hint;
|
||||
} else {
|
||||
self.hints.push(((id.to_string(), element), hint));
|
||||
@@ -382,9 +394,19 @@ impl NodeCore {
|
||||
|
||||
/// Set this node's position in `context` (C++
|
||||
/// `set_node_position_in_context`). Returns true when newly added.
|
||||
pub fn set_context_position(&mut self, context: NodeId, x: f64, y: f64, expanded: bool) -> bool {
|
||||
pub fn set_context_position(
|
||||
&mut self,
|
||||
context: NodeId,
|
||||
x: f64,
|
||||
y: f64,
|
||||
expanded: bool,
|
||||
) -> bool {
|
||||
let added = !self.context_contains(context);
|
||||
if let Some(slot) = self.context_positions.iter_mut().find(|(c, _, _)| *c == context) {
|
||||
if let Some(slot) = self
|
||||
.context_positions
|
||||
.iter_mut()
|
||||
.find(|(c, _, _)| *c == context)
|
||||
{
|
||||
slot.1 = (x, y);
|
||||
slot.2 = expanded;
|
||||
} else {
|
||||
@@ -501,7 +523,12 @@ pub trait NodeBehavior: Send {
|
||||
|
||||
/// Render-time connection resolution (C++
|
||||
/// `get_connected_render_output()`; Group overrides).
|
||||
fn connected_render_output(&self, core: &NodeCore, input: &str, element: i32) -> Option<NodeId> {
|
||||
fn connected_render_output(
|
||||
&self,
|
||||
core: &NodeCore,
|
||||
input: &str,
|
||||
element: i32,
|
||||
) -> Option<NodeId> {
|
||||
let _ = (core, input, element);
|
||||
None
|
||||
}
|
||||
@@ -509,19 +536,37 @@ pub trait NodeBehavior: Send {
|
||||
/// Time adjustment through this node (C++
|
||||
/// `input_time_adjustment()`/`output_time_adjustment()`; clips
|
||||
/// override for speed/reverse).
|
||||
fn input_time_adjustment(&self, input: &str, element: i32, time: TimeRange, traverse: bool) -> TimeRange {
|
||||
fn input_time_adjustment(
|
||||
&self,
|
||||
input: &str,
|
||||
element: i32,
|
||||
time: TimeRange,
|
||||
traverse: bool,
|
||||
) -> TimeRange {
|
||||
let _ = (input, element, traverse);
|
||||
time
|
||||
}
|
||||
|
||||
/// Output-side time adjustment.
|
||||
fn output_time_adjustment(&self, input: &str, element: i32, time: TimeRange, traverse: bool) -> TimeRange {
|
||||
fn output_time_adjustment(
|
||||
&self,
|
||||
input: &str,
|
||||
element: i32,
|
||||
time: TimeRange,
|
||||
traverse: bool,
|
||||
) -> TimeRange {
|
||||
let _ = (input, element, traverse);
|
||||
time
|
||||
}
|
||||
|
||||
/// Evaluate outputs (C++ `value()`).
|
||||
fn value(&self, core: &NodeCore, inputs: &NodeValueRow, time: Rational, table: &mut NodeValueTable) {
|
||||
fn value(
|
||||
&self,
|
||||
core: &NodeCore,
|
||||
inputs: &NodeValueRow,
|
||||
time: Rational,
|
||||
table: &mut NodeValueTable,
|
||||
) {
|
||||
let _ = (core, inputs, time, table);
|
||||
}
|
||||
|
||||
@@ -538,7 +583,12 @@ pub trait NodeBehavior: Send {
|
||||
|
||||
/// Direct frame generation (C++ `generate_frame()`; CPU-render
|
||||
/// nodes).
|
||||
fn generate_frame(&self, core: &NodeCore, frame: &mut crate::bridge::render::TextureHandle, time: Rational) {
|
||||
fn generate_frame(
|
||||
&self,
|
||||
core: &NodeCore,
|
||||
frame: &mut crate::bridge::render::TextureHandle,
|
||||
time: Rational,
|
||||
) {
|
||||
let _ = (core, frame, time);
|
||||
}
|
||||
|
||||
@@ -570,7 +620,13 @@ pub trait NodeBehavior: Send {
|
||||
}
|
||||
|
||||
/// Edge disconnected from an input (C++ `InputDisconnectedEvent`).
|
||||
fn input_disconnected(&mut self, core: &mut NodeCore, input: &str, element: i32, source: NodeId) {
|
||||
fn input_disconnected(
|
||||
&mut self,
|
||||
core: &mut NodeCore,
|
||||
input: &str,
|
||||
element: i32,
|
||||
source: NodeId,
|
||||
) {
|
||||
let _ = (core, input, element, source);
|
||||
}
|
||||
|
||||
@@ -581,7 +637,13 @@ pub trait NodeBehavior: Send {
|
||||
}
|
||||
|
||||
/// Output disconnected (C++ `OutputDisconnectedEvent`).
|
||||
fn output_disconnected(&mut self, core: &mut NodeCore, target: NodeId, input: &str, element: i32) {
|
||||
fn output_disconnected(
|
||||
&mut self,
|
||||
core: &mut NodeCore,
|
||||
target: NodeId,
|
||||
input: &str,
|
||||
element: i32,
|
||||
) {
|
||||
let _ = (core, target, input, element);
|
||||
}
|
||||
|
||||
@@ -610,7 +672,11 @@ pub trait NodeBehavior: Send {
|
||||
fn duplicate(&self, core: &NodeCore) -> Option<Box<dyn NodeBehavior>>;
|
||||
|
||||
/// Custom load/save (C++ `load_custom()`/`save_custom()`).
|
||||
fn load_custom(&mut self, core: &mut NodeCore, reader: &mut dyn crate::serializer::XmlRead) -> bool {
|
||||
fn load_custom(
|
||||
&mut self,
|
||||
core: &mut NodeCore,
|
||||
reader: &mut dyn crate::serializer::XmlRead,
|
||||
) -> bool {
|
||||
let _ = (core, reader);
|
||||
true
|
||||
}
|
||||
|
||||
@@ -423,9 +423,21 @@ impl BlurFilterNode {
|
||||
/// box/gaussian, `directional_degrees_in` only for directional, and
|
||||
/// `radial_center_in` only for radial.
|
||||
fn update_inputs(core: &mut NodeCore, method: i64) {
|
||||
set_hidden(core, HORIZ_INPUT, !(method == Method::Box as i64 || method == Method::Gaussian as i64));
|
||||
set_hidden(core, VERT_INPUT, !(method == Method::Box as i64 || method == Method::Gaussian as i64));
|
||||
set_hidden(core, DIRECTIONAL_DEGREES_INPUT, method != Method::Directional as i64);
|
||||
set_hidden(
|
||||
core,
|
||||
HORIZ_INPUT,
|
||||
!(method == Method::Box as i64 || method == Method::Gaussian as i64),
|
||||
);
|
||||
set_hidden(
|
||||
core,
|
||||
VERT_INPUT,
|
||||
!(method == Method::Box as i64 || method == Method::Gaussian as i64),
|
||||
);
|
||||
set_hidden(
|
||||
core,
|
||||
DIRECTIONAL_DEGREES_INPUT,
|
||||
method != Method::Directional as i64,
|
||||
);
|
||||
set_hidden(core, RADIAL_CENTER_INPUT, method != Method::Radial as i64);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,9 +443,12 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
crate::value::NodeValue::Boolean(false),
|
||||
));
|
||||
|
||||
(core, Box::new(ChromaKeyNode {
|
||||
base: crate::nodes::ociobase::OcioBase::new(),
|
||||
}))
|
||||
(
|
||||
core,
|
||||
Box::new(ChromaKeyNode {
|
||||
base: crate::nodes::ociobase::OcioBase::new(),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
/// Register this node type (C++ factory entry for
|
||||
@@ -488,21 +491,44 @@ mod tests {
|
||||
let (core, behavior) = create();
|
||||
assert_eq!(behavior.type_id(), "org.olivevideoeditor.Olive.chromakey");
|
||||
assert_ne!(
|
||||
core.get_input(crate::nodes::ociobase::TEXTURE_INPUT).unwrap().flags & crate::input::flags::NOT_KEYFRAMABLE,
|
||||
core.get_input(crate::nodes::ociobase::TEXTURE_INPUT)
|
||||
.unwrap()
|
||||
.flags & crate::input::flags::NOT_KEYFRAMABLE,
|
||||
0
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(COLOR_INPUT).unwrap().default,
|
||||
NodeValue::Color([0.0, 1.0, 0.0, 1.0])
|
||||
);
|
||||
assert_eq!(core.get_input(LOWER_TOLERANCE_INPUT).unwrap().default, NodeValue::Float(5.0));
|
||||
assert_eq!(core.get_input(UPPER_TOLERANCE_INPUT).unwrap().default, NodeValue::Float(25.0));
|
||||
assert_eq!(core.get_input(HIGHLIGHTS_INPUT).unwrap().default, NodeValue::Float(100.0));
|
||||
assert_eq!(core.get_input(SHADOWS_INPUT).unwrap().default, NodeValue::Float(100.0));
|
||||
assert_eq!(core.get_input(INVERT_INPUT).unwrap().default, NodeValue::Boolean(false));
|
||||
assert_eq!(core.get_input(MASK_ONLY_INPUT).unwrap().default, NodeValue::Boolean(false));
|
||||
assert_eq!(
|
||||
core.get_input(LOWER_TOLERANCE_INPUT).unwrap().default,
|
||||
NodeValue::Float(5.0)
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(UPPER_TOLERANCE_INPUT).unwrap().default,
|
||||
NodeValue::Float(25.0)
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(HIGHLIGHTS_INPUT).unwrap().default,
|
||||
NodeValue::Float(100.0)
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(SHADOWS_INPUT).unwrap().default,
|
||||
NodeValue::Float(100.0)
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(INVERT_INPUT).unwrap().default,
|
||||
NodeValue::Boolean(false)
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(MASK_ONLY_INPUT).unwrap().default,
|
||||
NodeValue::Boolean(false)
|
||||
);
|
||||
for id in [GARBAGE_MATTE_INPUT, CORE_MATTE_INPUT] {
|
||||
assert_ne!(core.get_input(id).unwrap().flags & crate::input::flags::NOT_KEYFRAMABLE, 0);
|
||||
assert_ne!(
|
||||
core.get_input(id).unwrap().flags & crate::input::flags::NOT_KEYFRAMABLE,
|
||||
0
|
||||
);
|
||||
}
|
||||
assert_eq!(core.effect_input, crate::nodes::ociobase::TEXTURE_INPUT);
|
||||
assert_ne!(core.flags & crate::node::flags::VIDEO_EFFECT, 0);
|
||||
@@ -524,16 +550,30 @@ mod tests {
|
||||
let n = ChromaKeyNode {
|
||||
base: crate::nodes::ociobase::OcioBase::new(),
|
||||
};
|
||||
assert_eq!(n.map_legacy_input_id("upper_tolerence_in"), UPPER_TOLERANCE_INPUT);
|
||||
assert_eq!(n.map_legacy_input_id("lower_tolerence_in"), LOWER_TOLERANCE_INPUT);
|
||||
assert_eq!(n.map_legacy_input_id("anything_else_in"), "anything_else_in");
|
||||
assert_eq!(
|
||||
n.map_legacy_input_id("upper_tolerence_in"),
|
||||
UPPER_TOLERANCE_INPUT
|
||||
);
|
||||
assert_eq!(
|
||||
n.map_legacy_input_id("lower_tolerence_in"),
|
||||
LOWER_TOLERANCE_INPUT
|
||||
);
|
||||
assert_eq!(
|
||||
n.map_legacy_input_id("anything_else_in"),
|
||||
"anything_else_in"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn value_no_texture_pushes_nothing() {
|
||||
let (core, behavior) = create();
|
||||
let mut table = NodeValueTable::default();
|
||||
behavior.value(&core, &crate::value::NodeValueRow::default(), Rational::new(0, 1), &mut table);
|
||||
behavior.value(
|
||||
&core,
|
||||
&crate::value::NodeValueRow::default(),
|
||||
Rational::new(0, 1),
|
||||
&mut table,
|
||||
);
|
||||
assert!(table.is_empty());
|
||||
}
|
||||
|
||||
@@ -557,7 +597,8 @@ mod tests {
|
||||
let mut node = ChromaKeyNode {
|
||||
base: crate::nodes::ociobase::OcioBase::new(),
|
||||
};
|
||||
node.base.set_processor(Some(crate::handle::CHandle::null()));
|
||||
node.base
|
||||
.set_processor(Some(crate::handle::CHandle::null()));
|
||||
let inputs = crate::value::NodeValueRow::from([(
|
||||
crate::nodes::ociobase::TEXTURE_INPUT.to_string(),
|
||||
NodeValue::Texture(crate::handle::CHandle::null()),
|
||||
|
||||
@@ -309,13 +309,28 @@ mod tests {
|
||||
#[test]
|
||||
fn create_wires_inputs_flags_and_properties() {
|
||||
let (core, behavior) = create();
|
||||
assert_eq!(behavior.type_id(), "org.olivevideoeditor.Olive.colordifferencekey");
|
||||
assert_eq!(
|
||||
behavior.type_id(),
|
||||
"org.olivevideoeditor.Olive.colordifferencekey"
|
||||
);
|
||||
for id in [TEXTURE_INPUT, GARBAGE_MATTE_INPUT, CORE_MATTE_INPUT] {
|
||||
assert_ne!(core.get_input(id).unwrap().flags & crate::input::flags::NOT_KEYFRAMABLE, 0);
|
||||
assert_ne!(
|
||||
core.get_input(id).unwrap().flags & crate::input::flags::NOT_KEYFRAMABLE,
|
||||
0
|
||||
);
|
||||
}
|
||||
assert_eq!(core.get_input(COLOR_INPUT).unwrap().default, NodeValue::Combo(0));
|
||||
assert_eq!(core.get_input(SHADOWS_INPUT).unwrap().default, NodeValue::Float(1.0));
|
||||
assert_eq!(core.get_input(HIGHLIGHTS_INPUT).unwrap().default, NodeValue::Float(1.0));
|
||||
assert_eq!(
|
||||
core.get_input(COLOR_INPUT).unwrap().default,
|
||||
NodeValue::Combo(0)
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(SHADOWS_INPUT).unwrap().default,
|
||||
NodeValue::Float(1.0)
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(HIGHLIGHTS_INPUT).unwrap().default,
|
||||
NodeValue::Float(1.0)
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(MASK_ONLY_INPUT).unwrap().default,
|
||||
NodeValue::Boolean(false)
|
||||
@@ -334,7 +349,12 @@ mod tests {
|
||||
fn value_no_texture_pushes_nothing() {
|
||||
let (core, behavior) = create();
|
||||
let mut table = NodeValueTable::default();
|
||||
behavior.value(&core, &crate::value::NodeValueRow::default(), Rational::new(0, 1), &mut table);
|
||||
behavior.value(
|
||||
&core,
|
||||
&crate::value::NodeValueRow::default(),
|
||||
Rational::new(0, 1),
|
||||
&mut table,
|
||||
);
|
||||
assert!(table.is_empty());
|
||||
}
|
||||
|
||||
|
||||
@@ -382,7 +382,11 @@ impl CornerPinDistortNode {
|
||||
/// origin, so corner 0 (top-left) maps straight, corner 1 (top-right)
|
||||
/// adds `(resolution.x, 0)`, corner 2 (bottom-right) adds the full
|
||||
/// resolution, and corner 3 (bottom-left) adds `(0, resolution.y)`.
|
||||
fn value_to_pixel(value: i32, row: &crate::value::NodeValueRow, resolution: (f64, f64)) -> (f64, f64) {
|
||||
fn value_to_pixel(
|
||||
value: i32,
|
||||
row: &crate::value::NodeValueRow,
|
||||
resolution: (f64, f64),
|
||||
) -> (f64, f64) {
|
||||
let vec_at = |id: &str| match row.get(id) {
|
||||
Some(crate::value::NodeValue::Vec2(v)) => *v,
|
||||
Some(v) => [v.to_double(), 0.0],
|
||||
@@ -591,26 +595,26 @@ mod tests {
|
||||
fn value_to_pixel_adds_resolution_origins() {
|
||||
let res = (1920.0, 1080.0);
|
||||
let mut row = crate::value::NodeValueRow::new();
|
||||
row.insert(
|
||||
TOP_LEFT_INPUT.to_string(),
|
||||
NodeValue::Vec2([5.0, 6.0]),
|
||||
row.insert(TOP_LEFT_INPUT.to_string(), NodeValue::Vec2([5.0, 6.0]));
|
||||
row.insert(TOP_RIGHT_INPUT.to_string(), NodeValue::Vec2([7.0, 8.0]));
|
||||
row.insert(BOTTOM_RIGHT_INPUT.to_string(), NodeValue::Vec2([9.0, 10.0]));
|
||||
row.insert(BOTTOM_LEFT_INPUT.to_string(), NodeValue::Vec2([11.0, 12.0]));
|
||||
assert_eq!(
|
||||
CornerPinDistortNode::value_to_pixel(0, &row, res),
|
||||
(5.0, 6.0)
|
||||
);
|
||||
row.insert(
|
||||
TOP_RIGHT_INPUT.to_string(),
|
||||
NodeValue::Vec2([7.0, 8.0]),
|
||||
assert_eq!(
|
||||
CornerPinDistortNode::value_to_pixel(1, &row, res),
|
||||
(1927.0, 8.0)
|
||||
);
|
||||
row.insert(
|
||||
BOTTOM_RIGHT_INPUT.to_string(),
|
||||
NodeValue::Vec2([9.0, 10.0]),
|
||||
assert_eq!(
|
||||
CornerPinDistortNode::value_to_pixel(2, &row, res),
|
||||
(1929.0, 1090.0)
|
||||
);
|
||||
row.insert(
|
||||
BOTTOM_LEFT_INPUT.to_string(),
|
||||
NodeValue::Vec2([11.0, 12.0]),
|
||||
assert_eq!(
|
||||
CornerPinDistortNode::value_to_pixel(3, &row, res),
|
||||
(11.0, 1092.0)
|
||||
);
|
||||
assert_eq!(CornerPinDistortNode::value_to_pixel(0, &row, res), (5.0, 6.0));
|
||||
assert_eq!(CornerPinDistortNode::value_to_pixel(1, &row, res), (1927.0, 8.0));
|
||||
assert_eq!(CornerPinDistortNode::value_to_pixel(2, &row, res), (1929.0, 1090.0));
|
||||
assert_eq!(CornerPinDistortNode::value_to_pixel(3, &row, res), (11.0, 1092.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -305,7 +305,10 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
drag_point: (0.0, 0.0),
|
||||
};
|
||||
let tl = Gizmo {
|
||||
position_inputs: vec![(LEFT_INPUT.to_string(), -1, 0), (TOP_INPUT.to_string(), -1, 0)],
|
||||
position_inputs: vec![
|
||||
(LEFT_INPUT.to_string(), -1, 0),
|
||||
(TOP_INPUT.to_string(), -1, 0),
|
||||
],
|
||||
drag_point: (0.0, 0.0),
|
||||
};
|
||||
let tc = Gizmo {
|
||||
@@ -313,11 +316,17 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
drag_point: (0.0, 0.0),
|
||||
};
|
||||
let tr = Gizmo {
|
||||
position_inputs: vec![(RIGHT_INPUT.to_string(), -1, 0), (TOP_INPUT.to_string(), -1, 0)],
|
||||
position_inputs: vec![
|
||||
(RIGHT_INPUT.to_string(), -1, 0),
|
||||
(TOP_INPUT.to_string(), -1, 0),
|
||||
],
|
||||
drag_point: (0.0, 0.0),
|
||||
};
|
||||
let bl = Gizmo {
|
||||
position_inputs: vec![(LEFT_INPUT.to_string(), -1, 0), (BOTTOM_INPUT.to_string(), -1, 0)],
|
||||
position_inputs: vec![
|
||||
(LEFT_INPUT.to_string(), -1, 0),
|
||||
(BOTTOM_INPUT.to_string(), -1, 0),
|
||||
],
|
||||
drag_point: (0.0, 0.0),
|
||||
};
|
||||
let bc = Gizmo {
|
||||
@@ -325,7 +334,10 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
drag_point: (0.0, 0.0),
|
||||
};
|
||||
let br = Gizmo {
|
||||
position_inputs: vec![(RIGHT_INPUT.to_string(), -1, 0), (BOTTOM_INPUT.to_string(), -1, 0)],
|
||||
position_inputs: vec![
|
||||
(RIGHT_INPUT.to_string(), -1, 0),
|
||||
(BOTTOM_INPUT.to_string(), -1, 0),
|
||||
],
|
||||
drag_point: (0.0, 0.0),
|
||||
};
|
||||
let cl = Gizmo {
|
||||
@@ -374,7 +386,10 @@ fn create_crop_side_input(core: &mut NodeCore, id: &str) {
|
||||
input.properties = vec![
|
||||
("min".to_string(), crate::value::NodeValue::Float(0.0)),
|
||||
("max".to_string(), crate::value::NodeValue::Float(1.0)),
|
||||
("view".to_string(), crate::value::NodeValue::Text("percentage".into())),
|
||||
(
|
||||
"view".to_string(),
|
||||
crate::value::NodeValue::Text("percentage".into()),
|
||||
),
|
||||
];
|
||||
core.add_input(input);
|
||||
}
|
||||
|
||||
@@ -272,9 +272,18 @@ mod tests {
|
||||
fn create_wires_inputs_flags_and_defaults() {
|
||||
let (core, behavior) = create();
|
||||
assert_eq!(behavior.type_id(), "org.olivevideoeditor.Olive.despill");
|
||||
assert_ne!(core.get_input(TEXTURE_INPUT).unwrap().flags & crate::input::flags::NOT_KEYFRAMABLE, 0);
|
||||
assert_eq!(core.get_input(COLOR_INPUT).unwrap().default, NodeValue::Combo(0));
|
||||
assert_eq!(core.get_input(METHOD_INPUT).unwrap().default, NodeValue::Combo(0));
|
||||
assert_ne!(
|
||||
core.get_input(TEXTURE_INPUT).unwrap().flags & crate::input::flags::NOT_KEYFRAMABLE,
|
||||
0
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(COLOR_INPUT).unwrap().default,
|
||||
NodeValue::Combo(0)
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(METHOD_INPUT).unwrap().default,
|
||||
NodeValue::Combo(0)
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(PRESERVE_LUMINANCE_INPUT).unwrap().default,
|
||||
NodeValue::Boolean(false)
|
||||
@@ -293,7 +302,12 @@ mod tests {
|
||||
fn value_no_texture_pushes_nothing() {
|
||||
let (core, behavior) = create();
|
||||
let mut table = NodeValueTable::default();
|
||||
behavior.value(&core, &crate::value::NodeValueRow::default(), Rational::new(0, 1), &mut table);
|
||||
behavior.value(
|
||||
&core,
|
||||
&crate::value::NodeValueRow::default(),
|
||||
Rational::new(0, 1),
|
||||
&mut table,
|
||||
);
|
||||
assert!(table.is_empty());
|
||||
}
|
||||
|
||||
|
||||
@@ -245,9 +245,12 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
core.add_input(combo);
|
||||
}
|
||||
|
||||
(core, Box::new(DisplayTransformNode {
|
||||
base: OcioBase::new(),
|
||||
}))
|
||||
(
|
||||
core,
|
||||
Box::new(DisplayTransformNode {
|
||||
base: OcioBase::new(),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
/// Register this node type (C++ factory entry for
|
||||
@@ -286,9 +289,14 @@ mod tests {
|
||||
#[test]
|
||||
fn create_wires_inputs_flags_and_properties() {
|
||||
let (core, behavior) = create();
|
||||
assert_eq!(behavior.type_id(), "org.olivevideoeditor.Olive.displaytransform");
|
||||
assert_eq!(
|
||||
behavior.type_id(),
|
||||
"org.olivevideoeditor.Olive.displaytransform"
|
||||
);
|
||||
assert_ne!(
|
||||
core.get_input(crate::nodes::ociobase::TEXTURE_INPUT).unwrap().flags & crate::input::flags::NOT_KEYFRAMABLE,
|
||||
core.get_input(crate::nodes::ociobase::TEXTURE_INPUT)
|
||||
.unwrap()
|
||||
.flags & crate::input::flags::NOT_KEYFRAMABLE,
|
||||
0
|
||||
);
|
||||
for id in [DISPLAY_INPUT, VIEW_INPUT, DIRECTION_INPUT] {
|
||||
@@ -324,7 +332,12 @@ mod tests {
|
||||
fn value_no_texture_pushes_nothing() {
|
||||
let (core, behavior) = create();
|
||||
let mut table = NodeValueTable::default();
|
||||
behavior.value(&core, &crate::value::NodeValueRow::default(), Rational::new(0, 1), &mut table);
|
||||
behavior.value(
|
||||
&core,
|
||||
&crate::value::NodeValueRow::default(),
|
||||
Rational::new(0, 1),
|
||||
&mut table,
|
||||
);
|
||||
assert!(table.is_empty());
|
||||
}
|
||||
|
||||
|
||||
@@ -233,7 +233,10 @@ impl NodeBehavior for DropShadowFilter {
|
||||
time: oakcore_rs::Rational,
|
||||
table: &mut crate::value::NodeValueTable,
|
||||
) {
|
||||
if !matches!(inputs.get(TEXTURE_INPUT), Some(crate::value::NodeValue::Texture(_))) {
|
||||
if !matches!(
|
||||
inputs.get(TEXTURE_INPUT),
|
||||
Some(crate::value::NodeValue::Texture(_))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let _ = (core, time, inputs);
|
||||
@@ -305,7 +308,10 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
);
|
||||
opacity.properties = vec![
|
||||
("min".to_string(), crate::value::NodeValue::Float(0.0)),
|
||||
("view".to_string(), crate::value::NodeValue::Text("percentage".into())),
|
||||
(
|
||||
"view".to_string(),
|
||||
crate::value::NodeValue::Text("percentage".into()),
|
||||
),
|
||||
];
|
||||
core.add_input(opacity);
|
||||
|
||||
|
||||
@@ -109,7 +109,11 @@ impl GeneratorWithMerge {
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
table.push(crate::value::ValueType::Texture, NodeValue::Texture(job), None);
|
||||
table.push(
|
||||
crate::value::ValueType::Texture,
|
||||
NodeValue::Texture(job),
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +117,11 @@ impl NodeGroup {
|
||||
/// Remove the passthrough (and the group input) for an inner
|
||||
/// input (C++ `remove_input_passthrough()`); no-op if absent.
|
||||
pub fn remove_input_passthrough(&mut self, core: &mut NodeCore, input: &InnerInput) {
|
||||
if let Some(i) = self.input_passthroughs.iter().position(|(_, inner)| inner == input) {
|
||||
if let Some(i) = self
|
||||
.input_passthroughs
|
||||
.iter()
|
||||
.position(|(_, inner)| inner == input)
|
||||
{
|
||||
let id = self.input_passthroughs.remove(i).0;
|
||||
core.remove_input(&id);
|
||||
}
|
||||
@@ -138,7 +142,9 @@ impl NodeGroup {
|
||||
/// Whether an inner input is already passed through (C++
|
||||
/// `contains_input_passthrough()`).
|
||||
pub fn contains_input_passthrough(&self, input: &InnerInput) -> bool {
|
||||
self.input_passthroughs.iter().any(|(_, inner)| inner == input)
|
||||
self.input_passthroughs
|
||||
.iter()
|
||||
.any(|(_, inner)| inner == input)
|
||||
}
|
||||
|
||||
/// Group input id for an inner input, or empty (C++
|
||||
@@ -251,7 +257,11 @@ impl NodeBehavior for NodeGroup {
|
||||
/// the C++ defers the node references into `SerializedData` and
|
||||
/// resolves them in `PostLoadEvent`, a channel this crate's
|
||||
/// serializer does not drive — `post_load` is therefore a no-op.
|
||||
fn load_custom(&mut self, _core: &mut NodeCore, reader: &mut dyn crate::serializer::XmlRead) -> bool {
|
||||
fn load_custom(
|
||||
&mut self,
|
||||
_core: &mut NodeCore,
|
||||
reader: &mut dyn crate::serializer::XmlRead,
|
||||
) -> bool {
|
||||
while reader.next_start_element() {
|
||||
match reader.name() {
|
||||
"inputpassthroughs" => {
|
||||
|
||||
@@ -407,9 +407,15 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
points.flags |= crate::input::flags::ARRAY;
|
||||
points.array_size = 5;
|
||||
core.add_input(points);
|
||||
for (i, (x, y)) in [(0.0, -135.0), (135.0, -45.0), (90.0, 120.0), (-90.0, 120.0), (-135.0, -45.0)]
|
||||
.iter()
|
||||
.enumerate()
|
||||
for (i, (x, y)) in [
|
||||
(0.0, -135.0),
|
||||
(135.0, -45.0),
|
||||
(90.0, 120.0),
|
||||
(-90.0, 120.0),
|
||||
(-135.0, -45.0),
|
||||
]
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
core.set_standard_value(
|
||||
crate::nodes::polygon::POINTS_INPUT,
|
||||
@@ -441,9 +447,12 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
feather.properties = vec![("min".to_string(), crate::value::NodeValue::Float(0.0))];
|
||||
core.add_input(feather);
|
||||
|
||||
(core, Box::new(MaskDistortNode {
|
||||
polygon: crate::nodes::polygon::PolygonGenerator,
|
||||
}))
|
||||
(
|
||||
core,
|
||||
Box::new(MaskDistortNode {
|
||||
polygon: crate::nodes::polygon::PolygonGenerator,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
/// Register this node type (C++ factory entry for
|
||||
@@ -468,7 +477,10 @@ mod tests {
|
||||
let n = MaskDistortNode {
|
||||
polygon: crate::nodes::polygon::PolygonGenerator,
|
||||
};
|
||||
assert_eq!(n.input_name(crate::nodes::generatorwithmerge::BASE_INPUT), "Texture");
|
||||
assert_eq!(
|
||||
n.input_name(crate::nodes::generatorwithmerge::BASE_INPUT),
|
||||
"Texture"
|
||||
);
|
||||
assert_eq!(n.input_name(INVERT_INPUT), "Invert");
|
||||
assert_eq!(n.input_name(FEATHER_INPUT), "Feather");
|
||||
assert_eq!(n.input_name(crate::nodes::polygon::POINTS_INPUT), "Points");
|
||||
@@ -482,10 +494,15 @@ mod tests {
|
||||
assert_eq!(behavior.type_id(), "org.olivevideoeditor.Olive.mask");
|
||||
// Inherited base wiring.
|
||||
assert_ne!(
|
||||
core.get_input(crate::nodes::generatorwithmerge::BASE_INPUT).unwrap().flags & crate::input::flags::NOT_KEYFRAMABLE,
|
||||
core.get_input(crate::nodes::generatorwithmerge::BASE_INPUT)
|
||||
.unwrap()
|
||||
.flags & crate::input::flags::NOT_KEYFRAMABLE,
|
||||
0
|
||||
);
|
||||
assert_eq!(core.effect_input, crate::nodes::generatorwithmerge::BASE_INPUT);
|
||||
assert_eq!(
|
||||
core.effect_input,
|
||||
crate::nodes::generatorwithmerge::BASE_INPUT
|
||||
);
|
||||
assert_ne!(core.flags & crate::node::flags::VIDEO_EFFECT, 0);
|
||||
// The inherited color input is hidden (mask is always white).
|
||||
let color = core.get_input(crate::nodes::polygon::COLOR_INPUT).unwrap();
|
||||
@@ -504,7 +521,10 @@ mod tests {
|
||||
NodeValue::Vec2([-135.0, -45.0])
|
||||
);
|
||||
// Mask-specific inputs.
|
||||
assert_eq!(core.get_input(INVERT_INPUT).unwrap().default, NodeValue::Boolean(false));
|
||||
assert_eq!(
|
||||
core.get_input(INVERT_INPUT).unwrap().default,
|
||||
NodeValue::Boolean(false)
|
||||
);
|
||||
let feather = core.get_input(FEATHER_INPUT).unwrap();
|
||||
assert_eq!(feather.default, NodeValue::Float(0.0));
|
||||
assert!(feather
|
||||
@@ -518,7 +538,12 @@ mod tests {
|
||||
let (core, behavior) = create();
|
||||
// No inputs at all: the matte is generated and pushed.
|
||||
let mut table = NodeValueTable::default();
|
||||
behavior.value(&core, &crate::value::NodeValueRow::default(), Rational::new(0, 1), &mut table);
|
||||
behavior.value(
|
||||
&core,
|
||||
&crate::value::NodeValueRow::default(),
|
||||
Rational::new(0, 1),
|
||||
&mut table,
|
||||
);
|
||||
assert!(table.get(ValueType::Texture).is_some());
|
||||
|
||||
// With a base texture: an "mrg" merge job is pushed instead.
|
||||
|
||||
@@ -186,8 +186,11 @@ impl NodeBehavior for MathNode {
|
||||
/// single-string return carries only the fragment shader
|
||||
/// (`// CPP-PARITY: math.cpp` `get_shader_code`).
|
||||
fn shader_code(&self, request: &str) -> Option<String> {
|
||||
let (frag, _vert) =
|
||||
super::mathbase::MathNodeBase::shader_code_internal(request, PARAM_A_INPUT, PARAM_B_INPUT);
|
||||
let (frag, _vert) = super::mathbase::MathNodeBase::shader_code_internal(
|
||||
request,
|
||||
PARAM_A_INPUT,
|
||||
PARAM_B_INPUT,
|
||||
);
|
||||
if frag.is_empty() {
|
||||
None
|
||||
} else {
|
||||
@@ -216,12 +219,10 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
crate::value::NodeValue::Combo(0),
|
||||
);
|
||||
method.flags |= crate::input::flags::NOT_CONNECTABLE | crate::input::flags::NOT_KEYFRAMABLE;
|
||||
method.properties = vec![
|
||||
(
|
||||
"combobox_strings".to_string(),
|
||||
crate::value::NodeValue::Binary(OPERATION_NAMES.concat().into_bytes()),
|
||||
),
|
||||
];
|
||||
method.properties = vec![(
|
||||
"combobox_strings".to_string(),
|
||||
crate::value::NodeValue::Binary(OPERATION_NAMES.concat().into_bytes()),
|
||||
)];
|
||||
core.add_input(method);
|
||||
|
||||
let mut a = crate::input::Input::new(
|
||||
@@ -230,11 +231,11 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
crate::value::NodeValue::Float(0.0),
|
||||
);
|
||||
a.properties = vec![
|
||||
("decimalplaces".to_string(), crate::value::NodeValue::Int(8)),
|
||||
(
|
||||
"decimalplaces".to_string(),
|
||||
crate::value::NodeValue::Int(8),
|
||||
"autotrim".to_string(),
|
||||
crate::value::NodeValue::Boolean(true),
|
||||
),
|
||||
("autotrim".to_string(), crate::value::NodeValue::Boolean(true)),
|
||||
];
|
||||
core.add_input(a);
|
||||
|
||||
@@ -244,11 +245,11 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
crate::value::NodeValue::Float(0.0),
|
||||
);
|
||||
b.properties = vec![
|
||||
("decimalplaces".to_string(), crate::value::NodeValue::Int(8)),
|
||||
(
|
||||
"decimalplaces".to_string(),
|
||||
crate::value::NodeValue::Int(8),
|
||||
"autotrim".to_string(),
|
||||
crate::value::NodeValue::Boolean(true),
|
||||
),
|
||||
("autotrim".to_string(), crate::value::NodeValue::Boolean(true)),
|
||||
];
|
||||
core.add_input(b);
|
||||
|
||||
@@ -341,7 +342,11 @@ mod tests {
|
||||
core.set_standard_value(METHOD_INPUT, -1, NodeValue::Combo(4));
|
||||
assert_eq!(n.operation(&core), super::super::mathbase::Operation::Power);
|
||||
core.set_standard_value(METHOD_INPUT, -1, NodeValue::Combo(99));
|
||||
assert_eq!(n.operation(&core), super::super::mathbase::Operation::Power, "clamped");
|
||||
assert_eq!(
|
||||
n.operation(&core),
|
||||
super::super::mathbase::Operation::Power,
|
||||
"clamped"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -109,8 +109,7 @@ impl PairingCalculator {
|
||||
if likelihood_a[i] == -1 || likelihood_b[i] == -1 {
|
||||
likelihoods[i] = -1;
|
||||
} else {
|
||||
likelihoods[i] =
|
||||
likelihood_a[i] + weight_a + likelihood_b[i] + weight_b;
|
||||
likelihoods[i] = likelihood_a[i] + weight_a + likelihood_b[i] + weight_b;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,8 +268,7 @@ impl MathNodeBase {
|
||||
match op {
|
||||
Operation::Add | Operation::Subtract => number == 0.0,
|
||||
Operation::Multiply | Operation::Divide | Operation::Power => {
|
||||
(number - 1.0f32).abs() * 100000.0f32
|
||||
<= number.abs().min(1.0f32)
|
||||
(number - 1.0f32).abs() * 100000.0f32 <= number.abs().min(1.0f32)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,7 +281,11 @@ impl MathNodeBase {
|
||||
/// instead emits a no-op fragment plus a vertex shader that
|
||||
/// multiplies `gl_Position` by the matrix uniform. Returns
|
||||
/// `(frag, vert)`.
|
||||
pub fn shader_code_internal(shader_id: &str, param_a_in: &str, param_b_in: &str) -> (String, String) {
|
||||
pub fn shader_code_internal(
|
||||
shader_id: &str,
|
||||
param_a_in: &str,
|
||||
param_b_in: &str,
|
||||
) -> (String, String) {
|
||||
let parts: Vec<i32> = shader_id
|
||||
.split('.')
|
||||
.map(|p| p.parse().unwrap_or(0))
|
||||
@@ -496,15 +498,21 @@ impl MathNodeBase {
|
||||
|
||||
Pairing::NumberColor => {
|
||||
let (col, num) = if val_a.value_type() == ValueType::Color {
|
||||
(match val_a {
|
||||
NodeValue::Color(c) => *c,
|
||||
_ => [0.0; 4],
|
||||
}, val_b.to_double())
|
||||
(
|
||||
match val_a {
|
||||
NodeValue::Color(c) => *c,
|
||||
_ => [0.0; 4],
|
||||
},
|
||||
val_b.to_double(),
|
||||
)
|
||||
} else {
|
||||
(match val_b {
|
||||
NodeValue::Color(c) => *c,
|
||||
_ => [0.0; 4],
|
||||
}, val_a.to_double())
|
||||
(
|
||||
match val_b {
|
||||
NodeValue::Color(c) => *c,
|
||||
_ => [0.0; 4],
|
||||
},
|
||||
val_a.to_double(),
|
||||
)
|
||||
};
|
||||
// Only multiply and divide are valid operations.
|
||||
let result = mult_color_number(operation, col, num);
|
||||
@@ -525,7 +533,10 @@ impl MathNodeBase {
|
||||
format: samples_a.format,
|
||||
channels,
|
||||
sample_count: max_samples,
|
||||
data: vec![0u8; max_samples * channels * samples_a.format.bytes_per_sample().max(1)],
|
||||
data: vec![
|
||||
0u8;
|
||||
max_samples * channels * samples_a.format.bytes_per_sample().max(1)
|
||||
],
|
||||
};
|
||||
|
||||
for c in 0..channels {
|
||||
@@ -556,7 +567,9 @@ impl MathNodeBase {
|
||||
output.push(ValueType::Samples, NodeValue::Samples(mixed), None);
|
||||
}
|
||||
|
||||
Pairing::TextureColor | Pairing::TextureNumber | Pairing::TextureTexture
|
||||
Pairing::TextureColor
|
||||
| Pairing::TextureNumber
|
||||
| Pairing::TextureTexture
|
||||
| Pairing::TextureMatrix => {
|
||||
let (number_val, texture_val) = if val_a.value_type() == ValueType::Texture {
|
||||
(val_b, val_a)
|
||||
@@ -592,11 +605,7 @@ impl MathNodeBase {
|
||||
|
||||
if operation_is_noop {
|
||||
// Just push texture as-is.
|
||||
output.push(
|
||||
ValueType::Texture,
|
||||
texture_val.clone(),
|
||||
None,
|
||||
);
|
||||
output.push(ValueType::Texture, texture_val.clone(), None);
|
||||
} else {
|
||||
// Push a texture-typed value representing the deferred
|
||||
// shader job. The C++ pushes `Texture::job(...)`
|
||||
@@ -687,11 +696,7 @@ impl MathNodeBase {
|
||||
let number_flt = Self::retrieve_number(number_val.unwrap());
|
||||
|
||||
for i in 0..output.channels {
|
||||
let v = perform_all_f32(
|
||||
operation,
|
||||
input.sample_value(i, index) as f32,
|
||||
number_flt,
|
||||
);
|
||||
let v = perform_all_f32(operation, input.sample_value(i, index) as f32, number_flt);
|
||||
output.set_sample_value(i, index, v as f64);
|
||||
}
|
||||
}
|
||||
@@ -722,7 +727,11 @@ fn perform_all_f32(operation: Operation, a: f32, b: f32) -> f32 {
|
||||
|
||||
/// `perform_add_sub_mult_div<Rational, Rational>` — add/sub/mul/div on
|
||||
/// rationals; power is unsupported and returns `a` unchanged.
|
||||
fn add_sub_mult_div_rational(operation: Operation, a: oakcore_rs::Rational, b: oakcore_rs::Rational) -> oakcore_rs::Rational {
|
||||
fn add_sub_mult_div_rational(
|
||||
operation: Operation,
|
||||
a: oakcore_rs::Rational,
|
||||
b: oakcore_rs::Rational,
|
||||
) -> oakcore_rs::Rational {
|
||||
match operation {
|
||||
Operation::Add => a + b,
|
||||
Operation::Subtract => a - b,
|
||||
@@ -746,27 +755,21 @@ fn retrieve_vector(val: &NodeValue) -> [f32; 4] {
|
||||
/// C++ `push_vector`: narrow a `[f32; 4]` back into the target vec type.
|
||||
fn push_vector(output: &mut NodeValueTable, ty: ValueType, vec: [f32; 4]) {
|
||||
match ty {
|
||||
ValueType::Vec2 => {
|
||||
output.push(
|
||||
ValueType::Vec2,
|
||||
NodeValue::Vec2([vec[0] as f64, vec[1] as f64]),
|
||||
None,
|
||||
)
|
||||
}
|
||||
ValueType::Vec3 => {
|
||||
output.push(
|
||||
ValueType::Vec3,
|
||||
NodeValue::Vec3([vec[0] as f64, vec[1] as f64, vec[2] as f64]),
|
||||
None,
|
||||
)
|
||||
}
|
||||
ValueType::Vec4 => {
|
||||
output.push(
|
||||
ValueType::Vec4,
|
||||
NodeValue::Vec4([vec[0] as f64, vec[1] as f64, vec[2] as f64, vec[3] as f64]),
|
||||
None,
|
||||
)
|
||||
}
|
||||
ValueType::Vec2 => output.push(
|
||||
ValueType::Vec2,
|
||||
NodeValue::Vec2([vec[0] as f64, vec[1] as f64]),
|
||||
None,
|
||||
),
|
||||
ValueType::Vec3 => output.push(
|
||||
ValueType::Vec3,
|
||||
NodeValue::Vec3([vec[0] as f64, vec[1] as f64, vec[2] as f64]),
|
||||
None,
|
||||
),
|
||||
ValueType::Vec4 => output.push(
|
||||
ValueType::Vec4,
|
||||
NodeValue::Vec4([vec[0] as f64, vec[1] as f64, vec[2] as f64, vec[3] as f64]),
|
||||
None,
|
||||
),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -912,9 +915,7 @@ fn is_scalar(val: Option<&NodeValue>) -> bool {
|
||||
match val {
|
||||
None => false,
|
||||
Some(NodeValue::None) => false,
|
||||
Some(v) => v
|
||||
.value_type()
|
||||
.is_numeric(),
|
||||
Some(v) => v.value_type().is_numeric(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -945,7 +946,10 @@ impl ValueType {
|
||||
|
||||
/// Whether the type is numeric (C++ `NodeValue::type_is_numeric`).
|
||||
pub fn is_numeric(self) -> bool {
|
||||
matches!(self, ValueType::Float | ValueType::Int | ValueType::Rational)
|
||||
matches!(
|
||||
self,
|
||||
ValueType::Float | ValueType::Int | ValueType::Rational
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -967,8 +971,14 @@ mod tests {
|
||||
#[test]
|
||||
fn operation_names() {
|
||||
assert_eq!(MathNodeBase::operation_name(Operation::Add), "Add");
|
||||
assert_eq!(MathNodeBase::operation_name(Operation::Subtract), "Subtract");
|
||||
assert_eq!(MathNodeBase::operation_name(Operation::Multiply), "Multiply");
|
||||
assert_eq!(
|
||||
MathNodeBase::operation_name(Operation::Subtract),
|
||||
"Subtract"
|
||||
);
|
||||
assert_eq!(
|
||||
MathNodeBase::operation_name(Operation::Multiply),
|
||||
"Multiply"
|
||||
);
|
||||
assert_eq!(MathNodeBase::operation_name(Operation::Divide), "Divide");
|
||||
assert_eq!(MathNodeBase::operation_name(Operation::Power), "Power");
|
||||
}
|
||||
@@ -983,7 +993,10 @@ mod tests {
|
||||
assert!(MathNodeBase::number_is_no_op(Operation::Multiply, 1.0));
|
||||
assert!(MathNodeBase::number_is_no_op(Operation::Divide, 1.0));
|
||||
assert!(MathNodeBase::number_is_no_op(Operation::Power, 1.0));
|
||||
assert!(MathNodeBase::number_is_no_op(Operation::Multiply, 1.0 + 0.0000001));
|
||||
assert!(MathNodeBase::number_is_no_op(
|
||||
Operation::Multiply,
|
||||
1.0 + 0.0000001
|
||||
));
|
||||
assert!(!MathNodeBase::number_is_no_op(Operation::Multiply, 2.0));
|
||||
assert!(!MathNodeBase::number_is_no_op(Operation::Divide, 0.0));
|
||||
}
|
||||
@@ -1139,7 +1152,10 @@ mod tests {
|
||||
&mut out,
|
||||
);
|
||||
// x' = 10*m[0][0] = 10; y' = 20*m[1][1] = 20.
|
||||
assert_eq!(out.get(ValueType::Vec2), Some(&NodeValue::Vec2([10.0, 20.0])));
|
||||
assert_eq!(
|
||||
out.get(ValueType::Vec2),
|
||||
Some(&NodeValue::Vec2([10.0, 20.0]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1268,15 +1284,13 @@ mod tests {
|
||||
&NodeValueRow::default(),
|
||||
&mut out,
|
||||
);
|
||||
assert_eq!(
|
||||
out.get(ValueType::Samples),
|
||||
Some(&NodeValue::Samples(buf))
|
||||
);
|
||||
assert_eq!(out.get(ValueType::Samples), Some(&NodeValue::Samples(buf)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shader_code_number_number_add() {
|
||||
let (frag, vert) = MathNodeBase::shader_code_internal("0.0.2.2", "param_a_in", "param_b_in");
|
||||
let (frag, vert) =
|
||||
MathNodeBase::shader_code_internal("0.0.2.2", "param_a_in", "param_b_in");
|
||||
assert!(vert.is_empty());
|
||||
assert!(frag.contains("uniform float param_a_in;"));
|
||||
assert!(frag.contains("uniform float param_b_in;"));
|
||||
@@ -1369,16 +1383,18 @@ mod tests {
|
||||
&NodeValueRow::default(),
|
||||
&mut out,
|
||||
);
|
||||
assert_eq!(out.get(ValueType::Texture), Some(&tex), "null texture passthrough");
|
||||
assert_eq!(
|
||||
out.get(ValueType::Texture),
|
||||
Some(&tex),
|
||||
"null texture passthrough"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn value_texture_number_identity_number_pushes_through() {
|
||||
// Non-null texture handle + identity number: noop -> passthrough.
|
||||
// Use a boxed handle so it is non-null but drops safely.
|
||||
let tex = crate::value::NodeValue::Texture(
|
||||
crate::handle::make_owned::<u8>(7),
|
||||
);
|
||||
let tex = crate::value::NodeValue::Texture(crate::handle::make_owned::<u8>(7));
|
||||
let mut out = NodeValueTable::default();
|
||||
MathNodeBase::value_internal(
|
||||
Operation::Multiply,
|
||||
@@ -1400,9 +1416,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn value_texture_number_job_placeholder() {
|
||||
let tex = crate::value::NodeValue::Texture(
|
||||
crate::handle::make_owned::<u8>(7),
|
||||
);
|
||||
let tex = crate::value::NodeValue::Texture(crate::handle::make_owned::<u8>(7));
|
||||
let mut out = NodeValueTable::default();
|
||||
MathNodeBase::value_internal(
|
||||
Operation::Multiply,
|
||||
@@ -1423,9 +1437,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn value_texture_matrix_identity_noop() {
|
||||
let tex = crate::value::NodeValue::Texture(
|
||||
crate::handle::make_owned::<u8>(7),
|
||||
);
|
||||
let tex = crate::value::NodeValue::Texture(crate::handle::make_owned::<u8>(7));
|
||||
let mut out = NodeValueTable::default();
|
||||
MathNodeBase::value_internal(
|
||||
Operation::Multiply,
|
||||
@@ -1448,13 +1460,14 @@ mod tests {
|
||||
fn value_sample_number_dynamic_passes_through() {
|
||||
// A keyframed number operand makes the input non-static.
|
||||
let mut core = NodeCore::new();
|
||||
core.keyframe_track_mut("num_in", -1).set_key(crate::keyframe::Keyframe {
|
||||
time: oakcore_rs::Rational::new(0, 1),
|
||||
value: NodeValue::Float(2.0),
|
||||
interpolation: crate::keyframe::Interpolation::Linear,
|
||||
bezier_in: (0.0, 0.0),
|
||||
bezier_out: (0.0, 0.0),
|
||||
});
|
||||
core.keyframe_track_mut("num_in", -1)
|
||||
.set_key(crate::keyframe::Keyframe {
|
||||
time: oakcore_rs::Rational::new(0, 1),
|
||||
value: NodeValue::Float(2.0),
|
||||
interpolation: crate::keyframe::Interpolation::Linear,
|
||||
bezier_in: (0.0, 0.0),
|
||||
bezier_out: (0.0, 0.0),
|
||||
});
|
||||
let buf = crate::value::SampleBuffer {
|
||||
format: oakcore_rs::SampleFormat::F32Planar,
|
||||
channels: 1,
|
||||
@@ -1546,10 +1559,30 @@ mod tests {
|
||||
fn value_rational_arithmetic_all_ops() {
|
||||
use oakcore_rs::Rational;
|
||||
for (op, a, b, expect) in [
|
||||
(Operation::Add, Rational::new(1, 2), Rational::new(1, 3), Rational::new(5, 6)),
|
||||
(Operation::Subtract, Rational::new(1, 2), Rational::new(1, 3), Rational::new(1, 6)),
|
||||
(Operation::Multiply, Rational::new(2, 3), Rational::new(3, 4), Rational::new(1, 2)),
|
||||
(Operation::Divide, Rational::new(1, 2), Rational::new(1, 4), Rational::new(2, 1)),
|
||||
(
|
||||
Operation::Add,
|
||||
Rational::new(1, 2),
|
||||
Rational::new(1, 3),
|
||||
Rational::new(5, 6),
|
||||
),
|
||||
(
|
||||
Operation::Subtract,
|
||||
Rational::new(1, 2),
|
||||
Rational::new(1, 3),
|
||||
Rational::new(1, 6),
|
||||
),
|
||||
(
|
||||
Operation::Multiply,
|
||||
Rational::new(2, 3),
|
||||
Rational::new(3, 4),
|
||||
Rational::new(1, 2),
|
||||
),
|
||||
(
|
||||
Operation::Divide,
|
||||
Rational::new(1, 2),
|
||||
Rational::new(1, 4),
|
||||
Rational::new(2, 1),
|
||||
),
|
||||
] {
|
||||
let mut out = NodeValueTable::default();
|
||||
MathNodeBase::value_internal(
|
||||
@@ -1563,13 +1596,18 @@ mod tests {
|
||||
&NodeValueRow::default(),
|
||||
&mut out,
|
||||
);
|
||||
assert_eq!(out.get(ValueType::Rational), Some(&NodeValue::Rational(expect)));
|
||||
assert_eq!(
|
||||
out.get(ValueType::Rational),
|
||||
Some(&NodeValue::Rational(expect))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pairing_calculator_sample_and_texture() {
|
||||
let a = table(vec![NodeValue::Samples(crate::value::SampleBuffer::default())]);
|
||||
let a = table(vec![NodeValue::Samples(
|
||||
crate::value::SampleBuffer::default(),
|
||||
)]);
|
||||
let b = table(vec![NodeValue::Float(1.0)]);
|
||||
let p = PairingCalculator::new(&a, &b);
|
||||
assert_eq!(p.most_likely_pairing, Pairing::SampleNumber);
|
||||
@@ -1640,21 +1678,32 @@ mod tests {
|
||||
&mut output2,
|
||||
0,
|
||||
);
|
||||
assert_eq!(output2.sample_value(0, 0), 6.0, "3 * 2 from the fallback scalar");
|
||||
assert_eq!(
|
||||
output2.sample_value(0, 0),
|
||||
6.0,
|
||||
"3 * 2 from the fallback scalar"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retrieve_number_combo_and_bool() {
|
||||
assert_eq!(MathNodeBase::retrieve_number(&NodeValue::Combo(3)), 3.0);
|
||||
assert_eq!(MathNodeBase::retrieve_number(&NodeValue::Boolean(true)), 1.0);
|
||||
assert_eq!(
|
||||
MathNodeBase::retrieve_number(&NodeValue::Boolean(true)),
|
||||
1.0
|
||||
);
|
||||
assert_eq!(MathNodeBase::retrieve_number(&NodeValue::None), 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_scalar_checks() {
|
||||
assert!(is_scalar(Some(&NodeValue::Float(1.0))));
|
||||
assert!(is_scalar(Some(&NodeValue::Rational(oakcore_rs::Rational::new(1, 2)))));
|
||||
assert!(!is_scalar(Some(&NodeValue::Samples(crate::value::SampleBuffer::default()))));
|
||||
assert!(is_scalar(Some(&NodeValue::Rational(
|
||||
oakcore_rs::Rational::new(1, 2)
|
||||
))));
|
||||
assert!(!is_scalar(Some(&NodeValue::Samples(
|
||||
crate::value::SampleBuffer::default()
|
||||
))));
|
||||
assert!(!is_scalar(Some(&NodeValue::None)));
|
||||
assert!(!is_scalar(None));
|
||||
}
|
||||
|
||||
@@ -127,9 +127,10 @@ impl NodeBehavior for MatrixGenerator {
|
||||
let uniform = core.standard_value(UNIFORM_SCALE_INPUT, -1).to_double() != 0.0;
|
||||
if let Some(scale) = core.get_input_mut(SCALE_INPUT) {
|
||||
scale.properties.retain(|(k, _)| k != "disable1");
|
||||
scale
|
||||
.properties
|
||||
.push(("disable1".to_string(), crate::value::NodeValue::Boolean(uniform)));
|
||||
scale.properties.push((
|
||||
"disable1".to_string(),
|
||||
crate::value::NodeValue::Boolean(uniform),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -315,7 +316,10 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
crate::value::ValueType::Vec2,
|
||||
crate::value::NodeValue::Vec2([0.0, 0.0]),
|
||||
);
|
||||
pos.properties = vec![("view".to_string(), crate::value::NodeValue::Text("percentage".into()))];
|
||||
pos.properties = vec![(
|
||||
"view".to_string(),
|
||||
crate::value::NodeValue::Text("percentage".into()),
|
||||
)];
|
||||
core.add_input(pos);
|
||||
|
||||
let mut rot = crate::input::Input::new(
|
||||
@@ -324,7 +328,10 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
crate::value::NodeValue::Float(0.0),
|
||||
);
|
||||
rot.properties = vec![
|
||||
("view".to_string(), crate::value::NodeValue::Text("percentage".into())),
|
||||
(
|
||||
"view".to_string(),
|
||||
crate::value::NodeValue::Text("percentage".into()),
|
||||
),
|
||||
("min".to_string(), crate::value::NodeValue::Float(-360.0)),
|
||||
("max".to_string(), crate::value::NodeValue::Float(360.0)),
|
||||
];
|
||||
@@ -337,8 +344,14 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
);
|
||||
scale.properties = vec![
|
||||
("min".to_string(), crate::value::NodeValue::Vec2([0.0, 0.0])),
|
||||
("view".to_string(), crate::value::NodeValue::Text("percentage".into())),
|
||||
("disable1".to_string(), crate::value::NodeValue::Boolean(true)),
|
||||
(
|
||||
"view".to_string(),
|
||||
crate::value::NodeValue::Text("percentage".into()),
|
||||
),
|
||||
(
|
||||
"disable1".to_string(),
|
||||
crate::value::NodeValue::Boolean(true),
|
||||
),
|
||||
];
|
||||
core.add_input(scale);
|
||||
|
||||
@@ -355,7 +368,10 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
crate::value::ValueType::Vec2,
|
||||
crate::value::NodeValue::Vec2([0.0, 0.0]),
|
||||
);
|
||||
anchor.properties = vec![("view".to_string(), crate::value::NodeValue::Text("percentage".into()))];
|
||||
anchor.properties = vec![(
|
||||
"view".to_string(),
|
||||
crate::value::NodeValue::Text("percentage".into()),
|
||||
)];
|
||||
core.add_input(anchor);
|
||||
|
||||
(core, Box::new(MatrixGenerator))
|
||||
@@ -521,16 +537,18 @@ mod tests {
|
||||
let mut b = behavior;
|
||||
b.input_value_changed(&mut core, UNIFORM_SCALE_INPUT, -1);
|
||||
let scale = core.get_input(SCALE_INPUT).unwrap();
|
||||
assert!(scale.properties.iter().any(|(k, v)| {
|
||||
k == "disable1" && *v == NodeValue::Boolean(true)
|
||||
}));
|
||||
assert!(scale
|
||||
.properties
|
||||
.iter()
|
||||
.any(|(k, v)| { k == "disable1" && *v == NodeValue::Boolean(true) }));
|
||||
|
||||
core.set_standard_value(UNIFORM_SCALE_INPUT, -1, NodeValue::Boolean(false));
|
||||
b.input_value_changed(&mut core, UNIFORM_SCALE_INPUT, -1);
|
||||
let scale = core.get_input(SCALE_INPUT).unwrap();
|
||||
assert!(scale.properties.iter().any(|(k, v)| {
|
||||
k == "disable1" && *v == NodeValue::Boolean(false)
|
||||
}));
|
||||
assert!(scale
|
||||
.properties
|
||||
.iter()
|
||||
.any(|(k, v)| { k == "disable1" && *v == NodeValue::Boolean(false) }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -136,7 +136,10 @@ impl NodeBehavior for MergeNode {
|
||||
let blend = inputs.get(BLEND_INPUT);
|
||||
|
||||
match (base, blend) {
|
||||
(Some(b @ crate::value::NodeValue::Texture(_)), Some(bl @ crate::value::NodeValue::Texture(_))) => {
|
||||
(
|
||||
Some(b @ crate::value::NodeValue::Texture(_)),
|
||||
Some(bl @ crate::value::NodeValue::Texture(_)),
|
||||
) => {
|
||||
// Both present: alpha-over shader job. The C++ checks
|
||||
// the blend channel count here (RGBA required for an
|
||||
// alpha to over with) and pushes the blend as-is when it
|
||||
@@ -229,7 +232,12 @@ mod tests {
|
||||
fn value_neither_pushes_nothing() {
|
||||
let (core, behavior) = create();
|
||||
let mut table = NodeValueTable::default();
|
||||
behavior.value(&core, &crate::value::NodeValueRow::default(), Rational::new(0, 1), &mut table);
|
||||
behavior.value(
|
||||
&core,
|
||||
&crate::value::NodeValueRow::default(),
|
||||
Rational::new(0, 1),
|
||||
&mut table,
|
||||
);
|
||||
assert!(table.is_empty());
|
||||
}
|
||||
|
||||
|
||||
@@ -129,7 +129,10 @@ impl NodeBehavior for MosaicFilterNode {
|
||||
time: oakcore_rs::Rational,
|
||||
table: &mut crate::value::NodeValueTable,
|
||||
) {
|
||||
if !matches!(inputs.get(TEXTURE_INPUT), Some(crate::value::NodeValue::Texture(_))) {
|
||||
if !matches!(
|
||||
inputs.get(TEXTURE_INPUT),
|
||||
Some(crate::value::NodeValue::Texture(_))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let _ = (core, time, inputs);
|
||||
@@ -211,7 +214,10 @@ mod tests {
|
||||
#[test]
|
||||
fn create_wires_inputs_and_flags() {
|
||||
let (core, behavior) = create();
|
||||
assert_eq!(behavior.type_id(), "org.olivevideoeditor.Olive.mosaicfilter");
|
||||
assert_eq!(
|
||||
behavior.type_id(),
|
||||
"org.olivevideoeditor.Olive.mosaicfilter"
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(HORIZ_INPUT).unwrap().default,
|
||||
NodeValue::Float(32.0)
|
||||
|
||||
@@ -58,7 +58,8 @@ pub struct MultiCamNode {
|
||||
|
||||
/// The C++ `k_input_flag_static` mask: not-connectable +
|
||||
/// not-keyframable.
|
||||
const STATIC_FLAGS: u32 = crate::input::flags::NOT_CONNECTABLE | crate::input::flags::NOT_KEYFRAMABLE;
|
||||
const STATIC_FLAGS: u32 =
|
||||
crate::input::flags::NOT_CONNECTABLE | crate::input::flags::NOT_KEYFRAMABLE;
|
||||
|
||||
impl MultiCamNode {
|
||||
/// Index of the currently selected source (C++
|
||||
@@ -208,7 +209,12 @@ impl NodeBehavior for MultiCamNode {
|
||||
/// `is_input_connected_for_render()` (reports `sources_in` elements
|
||||
/// as connected whenever a sequence is set); the trait has no such
|
||||
/// method, so that behavior folds into this one.
|
||||
fn connected_render_output(&self, core: &NodeCore, input: &str, element: i32) -> Option<NodeId> {
|
||||
fn connected_render_output(
|
||||
&self,
|
||||
core: &NodeCore,
|
||||
input: &str,
|
||||
element: i32,
|
||||
) -> Option<NodeId> {
|
||||
if self.sequence.is_some() && input == SOURCES_INPUT && element >= 0 {
|
||||
let _ = (core, element);
|
||||
None
|
||||
@@ -225,7 +231,13 @@ impl NodeBehavior for MultiCamNode {
|
||||
/// The Rust row carries no array payload (the `sources_in` value is
|
||||
/// the single active element), so the connected value is pushed
|
||||
/// through as-is; when the input is absent nothing is pushed.
|
||||
fn value(&self, core: &NodeCore, inputs: &NodeValueRow, time: Rational, table: &mut NodeValueTable) {
|
||||
fn value(
|
||||
&self,
|
||||
core: &NodeCore,
|
||||
inputs: &NodeValueRow,
|
||||
time: Rational,
|
||||
table: &mut NodeValueTable,
|
||||
) {
|
||||
let _ = (core, time);
|
||||
if let Some(v) = inputs.get(SOURCES_INPUT) {
|
||||
table.push(v.value_type(), v.clone(), None);
|
||||
@@ -251,7 +263,13 @@ impl NodeBehavior for MultiCamNode {
|
||||
/// Edge disconnected (C++ `InputDisconnectedEvent()`): on
|
||||
/// `sequence_in` disconnect, clears the stored sequence and re-hides
|
||||
/// `sequence_type_in`.
|
||||
fn input_disconnected(&mut self, core: &mut NodeCore, input: &str, element: i32, source: NodeId) {
|
||||
fn input_disconnected(
|
||||
&mut self,
|
||||
core: &mut NodeCore,
|
||||
input: &str,
|
||||
element: i32,
|
||||
source: NodeId,
|
||||
) {
|
||||
let _ = (element, source);
|
||||
if input == SEQUENCE_INPUT {
|
||||
if let Some(slot) = core.get_input_mut(SEQUENCE_TYPE_INPUT) {
|
||||
@@ -360,8 +378,14 @@ mod tests {
|
||||
fn create_wires_inputs() {
|
||||
let (core, behavior) = create();
|
||||
assert_eq!(behavior.type_id(), "org.olivevideoeditor.Olive.multicam");
|
||||
assert_eq!(core.get_input(CURRENT_INPUT).unwrap().value_type, ValueType::Combo);
|
||||
assert_eq!(core.get_input(CURRENT_INPUT).unwrap().default, NodeValue::Combo(0));
|
||||
assert_eq!(
|
||||
core.get_input(CURRENT_INPUT).unwrap().value_type,
|
||||
ValueType::Combo
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(CURRENT_INPUT).unwrap().default,
|
||||
NodeValue::Combo(0)
|
||||
);
|
||||
let sources = core.get_input(SOURCES_INPUT).unwrap();
|
||||
assert_ne!(sources.flags & crate::input::flags::ARRAY, 0);
|
||||
assert_ne!(sources.flags & crate::input::flags::NOT_KEYFRAMABLE, 0);
|
||||
@@ -414,7 +438,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn index_row_cols_round_trip() {
|
||||
for (i, rows, cols) in [(0, 3, 3), (1, 3, 3), (2, 3, 3), (3, 3, 3), (8, 3, 3), (5, 2, 3)] {
|
||||
for (i, rows, cols) in [
|
||||
(0, 3, 3),
|
||||
(1, 3, 3),
|
||||
(2, 3, 3),
|
||||
(3, 3, 3),
|
||||
(8, 3, 3),
|
||||
(5, 2, 3),
|
||||
] {
|
||||
let (r, c) = MultiCamNode::index_to_row_cols(i, rows, cols);
|
||||
assert_eq!(r, i / cols);
|
||||
assert_eq!(c, i % cols);
|
||||
@@ -439,7 +470,10 @@ mod tests {
|
||||
#[test]
|
||||
fn ignore_inputs_always_sequence() {
|
||||
let node = MultiCamNode { sequence: None };
|
||||
assert_eq!(node.ignore_inputs_for_rendering(), &[SEQUENCE_INPUT.to_string()]);
|
||||
assert_eq!(
|
||||
node.ignore_inputs_for_rendering(),
|
||||
&[SEQUENCE_INPUT.to_string()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -456,7 +490,12 @@ mod tests {
|
||||
fn value_pushes_nothing_when_sources_empty() {
|
||||
let (core, behavior) = create();
|
||||
let mut table = NodeValueTable::default();
|
||||
behavior.value(&core, &NodeValueRow::default(), Rational::new(0, 1), &mut table);
|
||||
behavior.value(
|
||||
&core,
|
||||
&NodeValueRow::default(),
|
||||
Rational::new(0, 1),
|
||||
&mut table,
|
||||
);
|
||||
assert!(table.is_empty());
|
||||
}
|
||||
|
||||
@@ -483,10 +522,16 @@ mod tests {
|
||||
#[test]
|
||||
fn duplicate_copies_sequence() {
|
||||
let seq = fake_id(7);
|
||||
let node = MultiCamNode { sequence: Some(seq) };
|
||||
let node = MultiCamNode {
|
||||
sequence: Some(seq),
|
||||
};
|
||||
let copy = node.duplicate(&NodeCore::new()).unwrap();
|
||||
assert_eq!(copy.type_id(), "org.olivevideoeditor.Olive.multicam");
|
||||
let down = copy.as_any().unwrap().downcast_ref::<MultiCamNode>().unwrap();
|
||||
let down = copy
|
||||
.as_any()
|
||||
.unwrap()
|
||||
.downcast_ref::<MultiCamNode>()
|
||||
.unwrap();
|
||||
assert_eq!(down.sequence, Some(seq));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,7 +186,10 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
crate::value::NodeValue::Float(0.2),
|
||||
);
|
||||
strength.properties = vec![
|
||||
("view".to_string(), crate::value::NodeValue::Text("percentage".into())),
|
||||
(
|
||||
"view".to_string(),
|
||||
crate::value::NodeValue::Text("percentage".into()),
|
||||
),
|
||||
("min".to_string(), crate::value::NodeValue::Float(0.0)),
|
||||
];
|
||||
core.add_input(strength);
|
||||
|
||||
@@ -83,12 +83,7 @@ pub struct OCIOGradingTransformLinearNode {
|
||||
}
|
||||
|
||||
/// Set or replace an input property (C++ `set_input_property`).
|
||||
fn set_input_property(
|
||||
core: &mut NodeCore,
|
||||
input: &str,
|
||||
key: &str,
|
||||
value: crate::value::NodeValue,
|
||||
) {
|
||||
fn set_input_property(core: &mut NodeCore, input: &str, key: &str, value: crate::value::NodeValue) {
|
||||
if let Some(input) = core.get_input_mut(input) {
|
||||
if let Some(slot) = input.properties.iter_mut().find(|(k, _)| k == key) {
|
||||
slot.1 = value;
|
||||
@@ -103,10 +98,30 @@ impl OCIOGradingTransformLinearNode {
|
||||
/// `set_vec4_input_colors()`): master `#c0c0c0`, R `#ff0000`, G
|
||||
/// `#00ff00`, B `#0000ff`.
|
||||
fn set_vec4_input_colors(core: &mut NodeCore, input: &str) {
|
||||
set_input_property(core, input, "color0", crate::value::NodeValue::Text("#c0c0c0".into()));
|
||||
set_input_property(core, input, "color1", crate::value::NodeValue::Text("#ff0000".into()));
|
||||
set_input_property(core, input, "color2", crate::value::NodeValue::Text("#00ff00".into()));
|
||||
set_input_property(core, input, "color3", crate::value::NodeValue::Text("#0000ff".into()));
|
||||
set_input_property(
|
||||
core,
|
||||
input,
|
||||
"color0",
|
||||
crate::value::NodeValue::Text("#c0c0c0".into()),
|
||||
);
|
||||
set_input_property(
|
||||
core,
|
||||
input,
|
||||
"color1",
|
||||
crate::value::NodeValue::Text("#ff0000".into()),
|
||||
);
|
||||
set_input_property(
|
||||
core,
|
||||
input,
|
||||
"color2",
|
||||
crate::value::NodeValue::Text("#00ff00".into()),
|
||||
);
|
||||
set_input_property(
|
||||
core,
|
||||
input,
|
||||
"color3",
|
||||
crate::value::NodeValue::Text("#0000ff".into()),
|
||||
);
|
||||
}
|
||||
|
||||
/// Constrain the white clamp UI minimum to just above the black
|
||||
@@ -129,7 +144,12 @@ impl OCIOGradingTransformLinearNode {
|
||||
return;
|
||||
}
|
||||
let min = core.standard_value(CLAMP_BLACK_INPUT, -1).to_double() + 0.000001;
|
||||
set_input_property(core, CLAMP_WHITE_INPUT, "min", crate::value::NodeValue::Float(min));
|
||||
set_input_property(
|
||||
core,
|
||||
CLAMP_WHITE_INPUT,
|
||||
"min",
|
||||
crate::value::NodeValue::Float(min),
|
||||
);
|
||||
}
|
||||
|
||||
/// (Re)build the color processor (C++ `generate_processor()`):
|
||||
@@ -212,7 +232,8 @@ impl NodeBehavior for OCIOGradingTransformLinearNode {
|
||||
CLAMP_WHITE_INPUT,
|
||||
"enabled",
|
||||
crate::value::NodeValue::Boolean(
|
||||
core.standard_value(CLAMP_WHITE_ENABLE_INPUT, -1).to_double() != 0.0,
|
||||
core.standard_value(CLAMP_WHITE_ENABLE_INPUT, -1)
|
||||
.to_double() != 0.0,
|
||||
),
|
||||
);
|
||||
} else if input == CLAMP_BLACK_ENABLE_INPUT {
|
||||
@@ -221,7 +242,8 @@ impl NodeBehavior for OCIOGradingTransformLinearNode {
|
||||
CLAMP_BLACK_INPUT,
|
||||
"enabled",
|
||||
crate::value::NodeValue::Boolean(
|
||||
core.standard_value(CLAMP_BLACK_ENABLE_INPUT, -1).to_double() != 0.0,
|
||||
core.standard_value(CLAMP_BLACK_ENABLE_INPUT, -1)
|
||||
.to_double() != 0.0,
|
||||
),
|
||||
);
|
||||
} else if input == CLAMP_BLACK_INPUT {
|
||||
@@ -236,7 +258,13 @@ impl NodeBehavior for OCIOGradingTransformLinearNode {
|
||||
/// Edge connected (C++ `InputConnectedEvent`): forwards to the base
|
||||
/// class and, for the black clamp input, re-constrains the white
|
||||
/// clamp minimum.
|
||||
fn input_connected(&mut self, core: &mut NodeCore, input: &str, element: i32, source: crate::id::NodeId) {
|
||||
fn input_connected(
|
||||
&mut self,
|
||||
core: &mut NodeCore,
|
||||
input: &str,
|
||||
element: i32,
|
||||
source: crate::id::NodeId,
|
||||
) {
|
||||
let _ = (element, source);
|
||||
// C++ forwards to the base class first; OCIOBaseNode does not
|
||||
// override the event, so that half is a no-op here.
|
||||
@@ -248,7 +276,13 @@ impl NodeBehavior for OCIOGradingTransformLinearNode {
|
||||
/// Edge disconnected (C++ `InputDisconnectedEvent`): forwards to the
|
||||
/// base class and, for the black clamp input, re-constrains the
|
||||
/// white clamp minimum.
|
||||
fn input_disconnected(&mut self, core: &mut NodeCore, input: &str, element: i32, source: crate::id::NodeId) {
|
||||
fn input_disconnected(
|
||||
&mut self,
|
||||
core: &mut NodeCore,
|
||||
input: &str,
|
||||
element: i32,
|
||||
source: crate::id::NodeId,
|
||||
) {
|
||||
let _ = (element, source);
|
||||
// See [`NodeBehavior::input_connected`].
|
||||
if input == CLAMP_BLACK_INPUT {
|
||||
@@ -349,10 +383,22 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
core.flags |= crate::node::flags::VIDEO_EFFECT;
|
||||
|
||||
let component_colors = vec![
|
||||
("color0".to_string(), crate::value::NodeValue::Text("#c0c0c0".into())),
|
||||
("color1".to_string(), crate::value::NodeValue::Text("#ff0000".into())),
|
||||
("color2".to_string(), crate::value::NodeValue::Text("#00ff00".into())),
|
||||
("color3".to_string(), crate::value::NodeValue::Text("#0000ff".into())),
|
||||
(
|
||||
"color0".to_string(),
|
||||
crate::value::NodeValue::Text("#c0c0c0".into()),
|
||||
),
|
||||
(
|
||||
"color1".to_string(),
|
||||
crate::value::NodeValue::Text("#ff0000".into()),
|
||||
),
|
||||
(
|
||||
"color2".to_string(),
|
||||
crate::value::NodeValue::Text("#00ff00".into()),
|
||||
),
|
||||
(
|
||||
"color3".to_string(),
|
||||
crate::value::NodeValue::Text("#0000ff".into()),
|
||||
),
|
||||
];
|
||||
|
||||
let mut contrast = crate::input::Input::new(
|
||||
@@ -394,7 +440,10 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
crate::value::NodeValue::Float(1.0),
|
||||
);
|
||||
saturation.properties = vec![
|
||||
("view".to_string(), crate::value::NodeValue::Text("percentage".into())),
|
||||
(
|
||||
"view".to_string(),
|
||||
crate::value::NodeValue::Text("percentage".into()),
|
||||
),
|
||||
("min".to_string(), crate::value::NodeValue::Float(0.0)),
|
||||
];
|
||||
core.add_input(saturation);
|
||||
@@ -421,7 +470,8 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
(
|
||||
"enabled".to_string(),
|
||||
crate::value::NodeValue::Boolean(
|
||||
core.standard_value(CLAMP_BLACK_ENABLE_INPUT, -1).to_double() != 0.0,
|
||||
core.standard_value(CLAMP_BLACK_ENABLE_INPUT, -1)
|
||||
.to_double() != 0.0,
|
||||
),
|
||||
),
|
||||
("base".to_string(), crate::value::NodeValue::Float(0.01)),
|
||||
@@ -442,7 +492,8 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
(
|
||||
"enabled".to_string(),
|
||||
crate::value::NodeValue::Boolean(
|
||||
core.standard_value(CLAMP_WHITE_ENABLE_INPUT, -1).to_double() != 0.0,
|
||||
core.standard_value(CLAMP_WHITE_ENABLE_INPUT, -1)
|
||||
.to_double() != 0.0,
|
||||
),
|
||||
),
|
||||
("base".to_string(), crate::value::NodeValue::Float(0.01)),
|
||||
@@ -487,8 +538,12 @@ mod tests {
|
||||
|
||||
/// Property value lookup helper for tests.
|
||||
fn property(core: &NodeCore, input: &str, key: &str) -> Option<NodeValue> {
|
||||
core.get_input(input)
|
||||
.and_then(|i| i.properties.iter().find(|(k, _)| k == key).map(|(_, v)| v.clone()))
|
||||
core.get_input(input).and_then(|i| {
|
||||
i.properties
|
||||
.iter()
|
||||
.find(|(k, _)| k == key)
|
||||
.map(|(_, v)| v.clone())
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -510,41 +565,79 @@ mod tests {
|
||||
#[test]
|
||||
fn create_wires_inputs_flags_and_properties() {
|
||||
let (core, behavior) = create();
|
||||
assert_eq!(behavior.type_id(), "org.olivevideoeditor.Olive.ociogradingtransformlinear");
|
||||
assert_eq!(
|
||||
behavior.type_id(),
|
||||
"org.olivevideoeditor.Olive.ociogradingtransformlinear"
|
||||
);
|
||||
assert_ne!(
|
||||
core.get_input(crate::nodes::ociobase::TEXTURE_INPUT).unwrap().flags & crate::input::flags::NOT_KEYFRAMABLE,
|
||||
core.get_input(crate::nodes::ociobase::TEXTURE_INPUT)
|
||||
.unwrap()
|
||||
.flags & crate::input::flags::NOT_KEYFRAMABLE,
|
||||
0
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(CONTRAST_INPUT).unwrap().default,
|
||||
NodeValue::Vec4([1.0, 1.0, 1.0, 1.0])
|
||||
);
|
||||
assert_eq!(core.get_input(OFFSET_INPUT).unwrap().default, NodeValue::Vec4([0.0; 4]));
|
||||
assert_eq!(core.get_input(EXPOSURE_INPUT).unwrap().default, NodeValue::Vec4([0.0; 4]));
|
||||
assert_eq!(core.get_input(SATURATION_INPUT).unwrap().default, NodeValue::Float(1.0));
|
||||
assert_eq!(core.get_input(PIVOT_INPUT).unwrap().default, NodeValue::Float(0.18));
|
||||
assert_eq!(
|
||||
core.get_input(OFFSET_INPUT).unwrap().default,
|
||||
NodeValue::Vec4([0.0; 4])
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(EXPOSURE_INPUT).unwrap().default,
|
||||
NodeValue::Vec4([0.0; 4])
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(SATURATION_INPUT).unwrap().default,
|
||||
NodeValue::Float(1.0)
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(PIVOT_INPUT).unwrap().default,
|
||||
NodeValue::Float(0.18)
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(CLAMP_BLACK_ENABLE_INPUT).unwrap().default,
|
||||
NodeValue::Boolean(false)
|
||||
);
|
||||
assert_eq!(core.get_input(CLAMP_BLACK_INPUT).unwrap().default, NodeValue::Float(0.0));
|
||||
assert_eq!(
|
||||
core.get_input(CLAMP_BLACK_INPUT).unwrap().default,
|
||||
NodeValue::Float(0.0)
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(CLAMP_WHITE_ENABLE_INPUT).unwrap().default,
|
||||
NodeValue::Boolean(false)
|
||||
);
|
||||
assert_eq!(core.get_input(CLAMP_WHITE_INPUT).unwrap().default, NodeValue::Float(1.0));
|
||||
assert_eq!(
|
||||
core.get_input(CLAMP_WHITE_INPUT).unwrap().default,
|
||||
NodeValue::Float(1.0)
|
||||
);
|
||||
// Component colors on every vec4 grading input.
|
||||
for id in [CONTRAST_INPUT, OFFSET_INPUT, EXPOSURE_INPUT] {
|
||||
let input = core.get_input(id).unwrap();
|
||||
assert!(input.properties.iter().any(|(k, v)| k == "color0" && *v == NodeValue::Text("#c0c0c0".into())));
|
||||
assert!(input.properties.iter().any(|(k, v)| k == "color3" && *v == NodeValue::Text("#0000ff".into())));
|
||||
assert!(input
|
||||
.properties
|
||||
.iter()
|
||||
.any(|(k, v)| k == "color0" && *v == NodeValue::Text("#c0c0c0".into())));
|
||||
assert!(input
|
||||
.properties
|
||||
.iter()
|
||||
.any(|(k, v)| k == "color3" && *v == NodeValue::Text("#0000ff".into())));
|
||||
}
|
||||
// Initial white-clamp minimum constraint: black (0.0) + 0.000001.
|
||||
assert_eq!(property(&core, CLAMP_WHITE_INPUT, "min"), Some(NodeValue::Float(0.000001)));
|
||||
assert_eq!(
|
||||
property(&core, CLAMP_WHITE_INPUT, "min"),
|
||||
Some(NodeValue::Float(0.000001))
|
||||
);
|
||||
// Clamp enabled properties mirror the enable inputs (false at
|
||||
// construction).
|
||||
assert_eq!(property(&core, CLAMP_BLACK_INPUT, "enabled"), Some(NodeValue::Boolean(false)));
|
||||
assert_eq!(property(&core, CLAMP_WHITE_INPUT, "enabled"), Some(NodeValue::Boolean(false)));
|
||||
assert_eq!(
|
||||
property(&core, CLAMP_BLACK_INPUT, "enabled"),
|
||||
Some(NodeValue::Boolean(false))
|
||||
);
|
||||
assert_eq!(
|
||||
property(&core, CLAMP_WHITE_INPUT, "enabled"),
|
||||
Some(NodeValue::Boolean(false))
|
||||
);
|
||||
assert_eq!(core.effect_input, crate::nodes::ociobase::TEXTURE_INPUT);
|
||||
assert_ne!(core.flags & crate::node::flags::VIDEO_EFFECT, 0);
|
||||
}
|
||||
@@ -566,7 +659,10 @@ mod tests {
|
||||
core.set_standard_value(CLAMP_BLACK_INPUT, -1, NodeValue::Float(0.5));
|
||||
let mut n = node();
|
||||
n.update_clamp_white_minimum(&mut core);
|
||||
assert_eq!(property(&core, CLAMP_WHITE_INPUT, "min"), Some(NodeValue::Float(0.500001)));
|
||||
assert_eq!(
|
||||
property(&core, CLAMP_WHITE_INPUT, "min"),
|
||||
Some(NodeValue::Float(0.500001))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -582,13 +678,14 @@ mod tests {
|
||||
crate::value::ValueType::Float,
|
||||
crate::value::NodeValue::Float(0.0),
|
||||
));
|
||||
core.keyframe_track_mut(CLAMP_BLACK_INPUT, -1).set_key(Keyframe {
|
||||
time: Rational::new(0, 1),
|
||||
value: NodeValue::Float(0.5),
|
||||
interpolation: Interpolation::Hold,
|
||||
bezier_in: (0.0, 0.0),
|
||||
bezier_out: (0.0, 0.0),
|
||||
});
|
||||
core.keyframe_track_mut(CLAMP_BLACK_INPUT, -1)
|
||||
.set_key(Keyframe {
|
||||
time: Rational::new(0, 1),
|
||||
value: NodeValue::Float(0.5),
|
||||
interpolation: Interpolation::Hold,
|
||||
bezier_in: (0.0, 0.0),
|
||||
bezier_out: (0.0, 0.0),
|
||||
});
|
||||
let mut n = node();
|
||||
n.update_clamp_white_minimum(&mut core);
|
||||
// Keyframed: the static minimum is not updated.
|
||||
@@ -621,11 +718,17 @@ mod tests {
|
||||
core.set_standard_value(CLAMP_BLACK_ENABLE_INPUT, -1, NodeValue::Boolean(true));
|
||||
let mut n = node();
|
||||
n.input_value_changed(&mut core, CLAMP_BLACK_ENABLE_INPUT, 0);
|
||||
assert_eq!(property(&core, CLAMP_BLACK_INPUT, "enabled"), Some(NodeValue::Boolean(true)));
|
||||
assert_eq!(
|
||||
property(&core, CLAMP_BLACK_INPUT, "enabled"),
|
||||
Some(NodeValue::Boolean(true))
|
||||
);
|
||||
// White enable mirrors too.
|
||||
core.set_standard_value(CLAMP_WHITE_ENABLE_INPUT, -1, NodeValue::Boolean(true));
|
||||
n.input_value_changed(&mut core, CLAMP_WHITE_ENABLE_INPUT, 0);
|
||||
assert_eq!(property(&core, CLAMP_WHITE_INPUT, "enabled"), Some(NodeValue::Boolean(true)));
|
||||
assert_eq!(
|
||||
property(&core, CLAMP_WHITE_INPUT, "enabled"),
|
||||
Some(NodeValue::Boolean(true))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -644,14 +747,22 @@ mod tests {
|
||||
core.set_standard_value(CLAMP_BLACK_INPUT, -1, NodeValue::Float(0.2));
|
||||
let mut n = node();
|
||||
n.input_value_changed(&mut core, CLAMP_BLACK_INPUT, 0);
|
||||
assert_eq!(property(&core, CLAMP_WHITE_INPUT, "min"), Some(NodeValue::Float(0.200001)));
|
||||
assert_eq!(
|
||||
property(&core, CLAMP_WHITE_INPUT, "min"),
|
||||
Some(NodeValue::Float(0.200001))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn value_no_texture_pushes_nothing() {
|
||||
let (core, behavior) = create();
|
||||
let mut table = NodeValueTable::default();
|
||||
behavior.value(&core, &crate::value::NodeValueRow::default(), Rational::new(0, 1), &mut table);
|
||||
behavior.value(
|
||||
&core,
|
||||
&crate::value::NodeValueRow::default(),
|
||||
Rational::new(0, 1),
|
||||
&mut table,
|
||||
);
|
||||
assert!(table.is_empty());
|
||||
}
|
||||
|
||||
|
||||
@@ -94,12 +94,7 @@ pub struct OCIOGradingTransformLogNode {
|
||||
}
|
||||
|
||||
/// Set or replace an input property (C++ `set_input_property`).
|
||||
fn set_input_property(
|
||||
core: &mut NodeCore,
|
||||
input: &str,
|
||||
key: &str,
|
||||
value: crate::value::NodeValue,
|
||||
) {
|
||||
fn set_input_property(core: &mut NodeCore, input: &str, key: &str, value: crate::value::NodeValue) {
|
||||
if let Some(input) = core.get_input_mut(input) {
|
||||
if let Some(slot) = input.properties.iter_mut().find(|(k, _)| k == key) {
|
||||
slot.1 = value;
|
||||
@@ -114,10 +109,30 @@ impl OCIOGradingTransformLogNode {
|
||||
/// `set_vec4_input_colors()`): master `#c0c0c0`, R `#ff0000`, G
|
||||
/// `#00ff00`, B `#0000ff`.
|
||||
fn set_vec4_input_colors(core: &mut NodeCore, input: &str) {
|
||||
set_input_property(core, input, "color0", crate::value::NodeValue::Text("#c0c0c0".into()));
|
||||
set_input_property(core, input, "color1", crate::value::NodeValue::Text("#ff0000".into()));
|
||||
set_input_property(core, input, "color2", crate::value::NodeValue::Text("#00ff00".into()));
|
||||
set_input_property(core, input, "color3", crate::value::NodeValue::Text("#0000ff".into()));
|
||||
set_input_property(
|
||||
core,
|
||||
input,
|
||||
"color0",
|
||||
crate::value::NodeValue::Text("#c0c0c0".into()),
|
||||
);
|
||||
set_input_property(
|
||||
core,
|
||||
input,
|
||||
"color1",
|
||||
crate::value::NodeValue::Text("#ff0000".into()),
|
||||
);
|
||||
set_input_property(
|
||||
core,
|
||||
input,
|
||||
"color2",
|
||||
crate::value::NodeValue::Text("#00ff00".into()),
|
||||
);
|
||||
set_input_property(
|
||||
core,
|
||||
input,
|
||||
"color3",
|
||||
crate::value::NodeValue::Text("#0000ff".into()),
|
||||
);
|
||||
}
|
||||
|
||||
/// Constrain the white clamp UI minimum to just above the black
|
||||
@@ -140,7 +155,12 @@ impl OCIOGradingTransformLogNode {
|
||||
return;
|
||||
}
|
||||
let min = core.standard_value(CLAMP_BLACK_INPUT, -1).to_double() + 0.000001;
|
||||
set_input_property(core, CLAMP_WHITE_INPUT, "min", crate::value::NodeValue::Float(min));
|
||||
set_input_property(
|
||||
core,
|
||||
CLAMP_WHITE_INPUT,
|
||||
"min",
|
||||
crate::value::NodeValue::Float(min),
|
||||
);
|
||||
}
|
||||
|
||||
/// (Re)build the color processor (C++ `generate_processor()`):
|
||||
@@ -223,7 +243,8 @@ impl NodeBehavior for OCIOGradingTransformLogNode {
|
||||
CLAMP_WHITE_INPUT,
|
||||
"enabled",
|
||||
crate::value::NodeValue::Boolean(
|
||||
core.standard_value(CLAMP_WHITE_ENABLE_INPUT, -1).to_double() != 0.0,
|
||||
core.standard_value(CLAMP_WHITE_ENABLE_INPUT, -1)
|
||||
.to_double() != 0.0,
|
||||
),
|
||||
);
|
||||
} else if input == CLAMP_BLACK_ENABLE_INPUT {
|
||||
@@ -232,7 +253,8 @@ impl NodeBehavior for OCIOGradingTransformLogNode {
|
||||
CLAMP_BLACK_INPUT,
|
||||
"enabled",
|
||||
crate::value::NodeValue::Boolean(
|
||||
core.standard_value(CLAMP_BLACK_ENABLE_INPUT, -1).to_double() != 0.0,
|
||||
core.standard_value(CLAMP_BLACK_ENABLE_INPUT, -1)
|
||||
.to_double() != 0.0,
|
||||
),
|
||||
);
|
||||
} else if input == CLAMP_BLACK_INPUT {
|
||||
@@ -247,7 +269,13 @@ impl NodeBehavior for OCIOGradingTransformLogNode {
|
||||
/// Edge connected (C++ `InputConnectedEvent`): forwards to the base
|
||||
/// class and, for the black clamp input, re-constrains the white
|
||||
/// clamp minimum.
|
||||
fn input_connected(&mut self, core: &mut NodeCore, input: &str, element: i32, source: crate::id::NodeId) {
|
||||
fn input_connected(
|
||||
&mut self,
|
||||
core: &mut NodeCore,
|
||||
input: &str,
|
||||
element: i32,
|
||||
source: crate::id::NodeId,
|
||||
) {
|
||||
let _ = (element, source);
|
||||
// C++ forwards to the base class first; OCIOBaseNode does not
|
||||
// override the event, so that half is a no-op here.
|
||||
@@ -259,7 +287,13 @@ impl NodeBehavior for OCIOGradingTransformLogNode {
|
||||
/// Edge disconnected (C++ `InputDisconnectedEvent`): forwards to the
|
||||
/// base class and, for the black clamp input, re-constrains the
|
||||
/// white clamp minimum.
|
||||
fn input_disconnected(&mut self, core: &mut NodeCore, input: &str, element: i32, source: crate::id::NodeId) {
|
||||
fn input_disconnected(
|
||||
&mut self,
|
||||
core: &mut NodeCore,
|
||||
input: &str,
|
||||
element: i32,
|
||||
source: crate::id::NodeId,
|
||||
) {
|
||||
let _ = (element, source);
|
||||
// See [`NodeBehavior::input_connected`].
|
||||
if input == CLAMP_BLACK_INPUT {
|
||||
@@ -360,10 +394,22 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
core.flags |= crate::node::flags::VIDEO_EFFECT;
|
||||
|
||||
let component_colors = vec![
|
||||
("color0".to_string(), crate::value::NodeValue::Text("#c0c0c0".into())),
|
||||
("color1".to_string(), crate::value::NodeValue::Text("#ff0000".into())),
|
||||
("color2".to_string(), crate::value::NodeValue::Text("#00ff00".into())),
|
||||
("color3".to_string(), crate::value::NodeValue::Text("#0000ff".into())),
|
||||
(
|
||||
"color0".to_string(),
|
||||
crate::value::NodeValue::Text("#c0c0c0".into()),
|
||||
),
|
||||
(
|
||||
"color1".to_string(),
|
||||
crate::value::NodeValue::Text("#ff0000".into()),
|
||||
),
|
||||
(
|
||||
"color2".to_string(),
|
||||
crate::value::NodeValue::Text("#00ff00".into()),
|
||||
),
|
||||
(
|
||||
"color3".to_string(),
|
||||
crate::value::NodeValue::Text("#0000ff".into()),
|
||||
),
|
||||
];
|
||||
|
||||
let mut lift = crate::input::Input::new(
|
||||
@@ -399,7 +445,10 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
crate::value::NodeValue::Float(1.0),
|
||||
);
|
||||
saturation.properties = vec![
|
||||
("view".to_string(), crate::value::NodeValue::Text("percentage".into())),
|
||||
(
|
||||
"view".to_string(),
|
||||
crate::value::NodeValue::Text("percentage".into()),
|
||||
),
|
||||
("min".to_string(), crate::value::NodeValue::Float(0.0)),
|
||||
];
|
||||
core.add_input(saturation);
|
||||
@@ -426,7 +475,8 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
(
|
||||
"enabled".to_string(),
|
||||
crate::value::NodeValue::Boolean(
|
||||
core.standard_value(CLAMP_BLACK_ENABLE_INPUT, -1).to_double() != 0.0,
|
||||
core.standard_value(CLAMP_BLACK_ENABLE_INPUT, -1)
|
||||
.to_double() != 0.0,
|
||||
),
|
||||
),
|
||||
("base".to_string(), crate::value::NodeValue::Float(0.01)),
|
||||
@@ -447,7 +497,8 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
(
|
||||
"enabled".to_string(),
|
||||
crate::value::NodeValue::Boolean(
|
||||
core.standard_value(CLAMP_WHITE_ENABLE_INPUT, -1).to_double() != 0.0,
|
||||
core.standard_value(CLAMP_WHITE_ENABLE_INPUT, -1)
|
||||
.to_double() != 0.0,
|
||||
),
|
||||
),
|
||||
("base".to_string(), crate::value::NodeValue::Float(0.01)),
|
||||
@@ -492,8 +543,12 @@ mod tests {
|
||||
|
||||
/// Property value lookup helper for tests.
|
||||
fn property(core: &NodeCore, input: &str, key: &str) -> Option<NodeValue> {
|
||||
core.get_input(input)
|
||||
.and_then(|i| i.properties.iter().find(|(k, _)| k == key).map(|(_, v)| v.clone()))
|
||||
core.get_input(input).and_then(|i| {
|
||||
i.properties
|
||||
.iter()
|
||||
.find(|(k, _)| k == key)
|
||||
.map(|(_, v)| v.clone())
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -515,26 +570,61 @@ mod tests {
|
||||
#[test]
|
||||
fn create_wires_inputs_flags_and_properties() {
|
||||
let (core, behavior) = create();
|
||||
assert_eq!(behavior.type_id(), "org.olivevideoeditor.Olive.OCIO_NAMESPACEgradingtransformlog");
|
||||
assert_eq!(
|
||||
behavior.type_id(),
|
||||
"org.olivevideoeditor.Olive.OCIO_NAMESPACEgradingtransformlog"
|
||||
);
|
||||
assert_ne!(
|
||||
core.get_input(crate::nodes::ociobase::TEXTURE_INPUT).unwrap().flags & crate::input::flags::NOT_KEYFRAMABLE,
|
||||
core.get_input(crate::nodes::ociobase::TEXTURE_INPUT)
|
||||
.unwrap()
|
||||
.flags & crate::input::flags::NOT_KEYFRAMABLE,
|
||||
0
|
||||
);
|
||||
assert_eq!(core.get_input(LIFT_INPUT).unwrap().default, NodeValue::Vec4([0.0; 4]));
|
||||
assert_eq!(core.get_input(GAIN_INPUT).unwrap().default, NodeValue::Vec4([1.0; 4]));
|
||||
assert_eq!(core.get_input(GAMMA_INPUT).unwrap().default, NodeValue::Vec4([1.0; 4]));
|
||||
assert_eq!(core.get_input(SATURATION_INPUT).unwrap().default, NodeValue::Float(1.0));
|
||||
assert_eq!(core.get_input(PIVOT_INPUT).unwrap().default, NodeValue::Float(-0.2));
|
||||
assert_eq!(core.get_input(CLAMP_BLACK_INPUT).unwrap().default, NodeValue::Float(0.0));
|
||||
assert_eq!(core.get_input(CLAMP_WHITE_INPUT).unwrap().default, NodeValue::Float(1.0));
|
||||
assert_eq!(
|
||||
core.get_input(LIFT_INPUT).unwrap().default,
|
||||
NodeValue::Vec4([0.0; 4])
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(GAIN_INPUT).unwrap().default,
|
||||
NodeValue::Vec4([1.0; 4])
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(GAMMA_INPUT).unwrap().default,
|
||||
NodeValue::Vec4([1.0; 4])
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(SATURATION_INPUT).unwrap().default,
|
||||
NodeValue::Float(1.0)
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(PIVOT_INPUT).unwrap().default,
|
||||
NodeValue::Float(-0.2)
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(CLAMP_BLACK_INPUT).unwrap().default,
|
||||
NodeValue::Float(0.0)
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(CLAMP_WHITE_INPUT).unwrap().default,
|
||||
NodeValue::Float(1.0)
|
||||
);
|
||||
// Component colors on every vec4 grading input.
|
||||
for id in [LIFT_INPUT, GAIN_INPUT, GAMMA_INPUT] {
|
||||
let input = core.get_input(id).unwrap();
|
||||
assert!(input.properties.iter().any(|(k, v)| k == "color0" && *v == NodeValue::Text("#c0c0c0".into())));
|
||||
assert!(input.properties.iter().any(|(k, v)| k == "color3" && *v == NodeValue::Text("#0000ff".into())));
|
||||
assert!(input
|
||||
.properties
|
||||
.iter()
|
||||
.any(|(k, v)| k == "color0" && *v == NodeValue::Text("#c0c0c0".into())));
|
||||
assert!(input
|
||||
.properties
|
||||
.iter()
|
||||
.any(|(k, v)| k == "color3" && *v == NodeValue::Text("#0000ff".into())));
|
||||
}
|
||||
// Initial white-clamp minimum constraint: black (0.0) + 0.000001.
|
||||
assert_eq!(property(&core, CLAMP_WHITE_INPUT, "min"), Some(NodeValue::Float(0.000001)));
|
||||
assert_eq!(
|
||||
property(&core, CLAMP_WHITE_INPUT, "min"),
|
||||
Some(NodeValue::Float(0.000001))
|
||||
);
|
||||
assert_eq!(core.effect_input, crate::nodes::ociobase::TEXTURE_INPUT);
|
||||
assert_ne!(core.flags & crate::node::flags::VIDEO_EFFECT, 0);
|
||||
}
|
||||
@@ -555,7 +645,10 @@ mod tests {
|
||||
core.set_standard_value(CLAMP_BLACK_INPUT, -1, NodeValue::Float(0.25));
|
||||
let mut n = node();
|
||||
n.update_clamp_white_minimum(&mut core);
|
||||
assert_eq!(property(&core, CLAMP_WHITE_INPUT, "min"), Some(NodeValue::Float(0.250001)));
|
||||
assert_eq!(
|
||||
property(&core, CLAMP_WHITE_INPUT, "min"),
|
||||
Some(NodeValue::Float(0.250001))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -571,13 +664,14 @@ mod tests {
|
||||
crate::value::ValueType::Float,
|
||||
crate::value::NodeValue::Float(0.0),
|
||||
));
|
||||
core.keyframe_track_mut(CLAMP_BLACK_INPUT, -1).set_key(Keyframe {
|
||||
time: Rational::new(0, 1),
|
||||
value: NodeValue::Float(0.5),
|
||||
interpolation: Interpolation::Hold,
|
||||
bezier_in: (0.0, 0.0),
|
||||
bezier_out: (0.0, 0.0),
|
||||
});
|
||||
core.keyframe_track_mut(CLAMP_BLACK_INPUT, -1)
|
||||
.set_key(Keyframe {
|
||||
time: Rational::new(0, 1),
|
||||
value: NodeValue::Float(0.5),
|
||||
interpolation: Interpolation::Hold,
|
||||
bezier_in: (0.0, 0.0),
|
||||
bezier_out: (0.0, 0.0),
|
||||
});
|
||||
let mut n = node();
|
||||
n.update_clamp_white_minimum(&mut core);
|
||||
assert_eq!(property(&core, CLAMP_WHITE_INPUT, "min"), None);
|
||||
@@ -599,7 +693,10 @@ mod tests {
|
||||
core.set_standard_value(CLAMP_BLACK_ENABLE_INPUT, -1, NodeValue::Boolean(true));
|
||||
let mut n = node();
|
||||
n.input_value_changed(&mut core, CLAMP_BLACK_ENABLE_INPUT, 0);
|
||||
assert_eq!(property(&core, CLAMP_BLACK_INPUT, "enabled"), Some(NodeValue::Boolean(true)));
|
||||
assert_eq!(
|
||||
property(&core, CLAMP_BLACK_INPUT, "enabled"),
|
||||
Some(NodeValue::Boolean(true))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -618,14 +715,22 @@ mod tests {
|
||||
core.set_standard_value(CLAMP_BLACK_INPUT, -1, NodeValue::Float(0.1));
|
||||
let mut n = node();
|
||||
n.input_value_changed(&mut core, CLAMP_BLACK_INPUT, 0);
|
||||
assert_eq!(property(&core, CLAMP_WHITE_INPUT, "min"), Some(NodeValue::Float(0.100001)));
|
||||
assert_eq!(
|
||||
property(&core, CLAMP_WHITE_INPUT, "min"),
|
||||
Some(NodeValue::Float(0.100001))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn value_no_texture_pushes_nothing() {
|
||||
let (core, behavior) = create();
|
||||
let mut table = NodeValueTable::default();
|
||||
behavior.value(&core, &crate::value::NodeValueRow::default(), Rational::new(0, 1), &mut table);
|
||||
behavior.value(
|
||||
&core,
|
||||
&crate::value::NodeValueRow::default(),
|
||||
Rational::new(0, 1),
|
||||
&mut table,
|
||||
);
|
||||
assert!(table.is_empty());
|
||||
}
|
||||
|
||||
|
||||
@@ -175,10 +175,7 @@ impl OCIOLutNode {
|
||||
{
|
||||
let state = self.state.lock().unwrap();
|
||||
if !state.dirty
|
||||
&& state
|
||||
.last_processor
|
||||
.as_ref()
|
||||
.is_some_and(|p| !p.is_null())
|
||||
&& state.last_processor.as_ref().is_some_and(|p| !p.is_null())
|
||||
&& Self::file_path(core) == state.last_path
|
||||
&& Self::read_direction_input(core) == state.last_direction
|
||||
{
|
||||
@@ -376,7 +373,10 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
"placeholder".to_string(),
|
||||
crate::value::NodeValue::Text("Select a LUT file".into()),
|
||||
),
|
||||
("lut_library".to_string(), crate::value::NodeValue::Boolean(true)),
|
||||
(
|
||||
"lut_library".to_string(),
|
||||
crate::value::NodeValue::Boolean(true),
|
||||
),
|
||||
];
|
||||
core.add_input(file);
|
||||
|
||||
@@ -435,7 +435,9 @@ mod tests {
|
||||
let (core, behavior) = create();
|
||||
assert_eq!(behavior.type_id(), "org.olivevideoeditor.Olive.ociolut");
|
||||
assert_ne!(
|
||||
core.get_input(crate::nodes::ociobase::TEXTURE_INPUT).unwrap().flags & crate::input::flags::NOT_KEYFRAMABLE,
|
||||
core.get_input(crate::nodes::ociobase::TEXTURE_INPUT)
|
||||
.unwrap()
|
||||
.flags & crate::input::flags::NOT_KEYFRAMABLE,
|
||||
0
|
||||
);
|
||||
let file = core.get_input(FILE_INPUT).unwrap();
|
||||
@@ -443,10 +445,8 @@ mod tests {
|
||||
assert_ne!(file.flags & crate::input::flags::NOT_KEYFRAMABLE, 0);
|
||||
assert_ne!(file.flags & crate::input::flags::NOT_CONNECTABLE, 0);
|
||||
// `*.*` filter fallback without the render bridge.
|
||||
assert!(file
|
||||
.properties
|
||||
.iter()
|
||||
.any(|(k, v)| k == "filter" && *v == NodeValue::Text("LUT Files (*.*);;All Files (*)".into())));
|
||||
assert!(file.properties.iter().any(|(k, v)| k == "filter"
|
||||
&& *v == NodeValue::Text("LUT Files (*.*);;All Files (*)".into())));
|
||||
assert!(file
|
||||
.properties
|
||||
.iter()
|
||||
@@ -625,7 +625,12 @@ mod tests {
|
||||
fn value_no_texture_pushes_nothing() {
|
||||
let (core, behavior) = create();
|
||||
let mut table = NodeValueTable::default();
|
||||
behavior.value(&core, &crate::value::NodeValueRow::default(), Rational::new(0, 1), &mut table);
|
||||
behavior.value(
|
||||
&core,
|
||||
&crate::value::NodeValueRow::default(),
|
||||
Rational::new(0, 1),
|
||||
&mut table,
|
||||
);
|
||||
assert!(table.is_empty());
|
||||
}
|
||||
|
||||
|
||||
@@ -230,7 +230,10 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
crate::value::NodeValue::Float(1.0),
|
||||
);
|
||||
opacity.properties = vec![
|
||||
("view".to_string(), crate::value::NodeValue::Text("percentage".into())),
|
||||
(
|
||||
"view".to_string(),
|
||||
crate::value::NodeValue::Text("percentage".into()),
|
||||
),
|
||||
("min".to_string(), crate::value::NodeValue::Float(0.0)),
|
||||
("max".to_string(), crate::value::NodeValue::Float(1.0)),
|
||||
];
|
||||
@@ -239,9 +242,12 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
core.flags |= crate::node::flags::VIDEO_EFFECT;
|
||||
core.effect_input = TEXTURE_INPUT.to_string();
|
||||
|
||||
(core, Box::new(OpacityEffect {
|
||||
math: super::math::MathNode::new(),
|
||||
}))
|
||||
(
|
||||
core,
|
||||
Box::new(OpacityEffect {
|
||||
math: super::math::MathNode::new(),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -264,7 +270,10 @@ mod tests {
|
||||
fn create_wires_inputs_and_flags() {
|
||||
let (core, behavior) = create();
|
||||
assert_eq!(behavior.type_id(), "org.olivevideoeditor.Olive.opacity");
|
||||
assert_eq!(core.get_input(VALUE_INPUT).unwrap().default, NodeValue::Float(1.0));
|
||||
assert_eq!(
|
||||
core.get_input(VALUE_INPUT).unwrap().default,
|
||||
NodeValue::Float(1.0)
|
||||
);
|
||||
assert_eq!(core.effect_input, TEXTURE_INPUT);
|
||||
assert_ne!(core.flags & crate::node::flags::VIDEO_EFFECT, 0);
|
||||
}
|
||||
@@ -273,7 +282,12 @@ mod tests {
|
||||
fn value_no_texture_pushes_nothing() {
|
||||
let (core, behavior) = create();
|
||||
let mut table = NodeValueTable::default();
|
||||
behavior.value(&core, &crate::value::NodeValueRow::default(), Rational::new(0, 1), &mut table);
|
||||
behavior.value(
|
||||
&core,
|
||||
&crate::value::NodeValueRow::default(),
|
||||
Rational::new(0, 1),
|
||||
&mut table,
|
||||
);
|
||||
assert!(table.is_empty());
|
||||
}
|
||||
|
||||
|
||||
@@ -139,7 +139,9 @@ impl NodeBehavior for PanNode {
|
||||
};
|
||||
let pan_val = match inputs.get(PANNING_INPUT) {
|
||||
Some(v) => v.to_double(),
|
||||
None => core.value_at_time(PANNING_INPUT, -1, range.in_()).to_double(),
|
||||
None => core
|
||||
.value_at_time(PANNING_INPUT, -1, range.in_())
|
||||
.to_double(),
|
||||
};
|
||||
|
||||
for c in 0..output.channels {
|
||||
@@ -175,19 +177,12 @@ impl NodeBehavior for PanNode {
|
||||
pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
let mut core = NodeCore::new();
|
||||
|
||||
let mut samples = crate::input::Input::new(
|
||||
SAMPLES_INPUT,
|
||||
ValueType::Samples,
|
||||
NodeValue::None,
|
||||
);
|
||||
let mut samples = crate::input::Input::new(SAMPLES_INPUT, ValueType::Samples, NodeValue::None);
|
||||
samples.flags |= crate::input::flags::NOT_KEYFRAMABLE;
|
||||
core.add_input(samples);
|
||||
|
||||
let mut panning = crate::input::Input::new(
|
||||
PANNING_INPUT,
|
||||
ValueType::Float,
|
||||
NodeValue::Float(0.0),
|
||||
);
|
||||
let mut panning =
|
||||
crate::input::Input::new(PANNING_INPUT, ValueType::Float, NodeValue::Float(0.0));
|
||||
panning.properties = vec![
|
||||
("min".to_string(), NodeValue::Float(-1.0)),
|
||||
("max".to_string(), NodeValue::Float(1.0)),
|
||||
@@ -233,7 +228,10 @@ mod tests {
|
||||
fn create_wires_inputs_and_flags() {
|
||||
let (core, behavior) = create();
|
||||
assert_eq!(behavior.type_id(), "org.olivevideoeditor.Olive.pan");
|
||||
assert_eq!(core.get_input(PANNING_INPUT).unwrap().default, NodeValue::Float(0.0));
|
||||
assert_eq!(
|
||||
core.get_input(PANNING_INPUT).unwrap().default,
|
||||
NodeValue::Float(0.0)
|
||||
);
|
||||
assert_eq!(core.effect_input, SAMPLES_INPUT);
|
||||
assert_ne!(core.flags & crate::node::flags::AUDIO_EFFECT, 0);
|
||||
}
|
||||
|
||||
@@ -139,7 +139,13 @@ impl NodeBehavior for PluginNode {
|
||||
/// The Rust model has no plugin-job payload: the job case pushes a
|
||||
/// null texture handle marking a renderer-deferred plugin job
|
||||
/// (`// CPP-PARITY: plugin.cpp` `value()`).
|
||||
fn value(&self, core: &NodeCore, inputs: &NodeValueRow, time: Rational, table: &mut NodeValueTable) {
|
||||
fn value(
|
||||
&self,
|
||||
core: &NodeCore,
|
||||
inputs: &NodeValueRow,
|
||||
time: Rational,
|
||||
table: &mut NodeValueTable,
|
||||
) {
|
||||
let _ = (core, time);
|
||||
|
||||
// Re-push every non-texture, non-none input value, tagged with
|
||||
@@ -156,7 +162,11 @@ impl NodeBehavior for PluginNode {
|
||||
let tex = inputs
|
||||
.get(SOURCE_CLIP)
|
||||
.filter(|v| matches!(v, NodeValue::Texture(_)))
|
||||
.or_else(|| inputs.get(TEXTURE_INPUT).filter(|v| matches!(v, NodeValue::Texture(_))))
|
||||
.or_else(|| {
|
||||
inputs
|
||||
.get(TEXTURE_INPUT)
|
||||
.filter(|v| matches!(v, NodeValue::Texture(_)))
|
||||
})
|
||||
.or_else(|| inputs.values().find(|v| matches!(v, NodeValue::Texture(_))));
|
||||
|
||||
if tex.is_some() && !self.instance.is_null() {
|
||||
@@ -236,7 +246,12 @@ impl NodeBehavior for PluginNode {
|
||||
/// whose pixels cannot be read or written from this crate, so the
|
||||
/// body is a documented no-op (`// CPP-PARITY: plugin.cpp`
|
||||
/// `generate_frame`).
|
||||
fn generate_frame(&self, core: &NodeCore, frame: &mut crate::bridge::render::TextureHandle, time: Rational) {
|
||||
fn generate_frame(
|
||||
&self,
|
||||
core: &NodeCore,
|
||||
frame: &mut crate::bridge::render::TextureHandle,
|
||||
time: Rational,
|
||||
) {
|
||||
let _ = (core, frame, time);
|
||||
}
|
||||
|
||||
@@ -379,8 +394,12 @@ mod tests {
|
||||
.map(|(_, v, t)| (t.as_deref().unwrap(), v))
|
||||
.collect();
|
||||
assert_eq!(tagged.len(), 2);
|
||||
assert!(tagged.iter().any(|(id, v)| *id == "opacity" && *v == &NodeValue::Float(0.5)));
|
||||
assert!(tagged.iter().any(|(id, v)| *id == "mode" && *v == &NodeValue::Combo(2)));
|
||||
assert!(tagged
|
||||
.iter()
|
||||
.any(|(id, v)| *id == "opacity" && *v == &NodeValue::Float(0.5)));
|
||||
assert!(tagged
|
||||
.iter()
|
||||
.any(|(id, v)| *id == "mode" && *v == &NodeValue::Combo(2)));
|
||||
// No texture pushed (no texture input in the row).
|
||||
assert!(table.get(ValueType::Texture).is_none());
|
||||
}
|
||||
@@ -390,7 +409,10 @@ mod tests {
|
||||
let n = node();
|
||||
let core = NodeCore::new();
|
||||
let mut row = NodeValueRow::default();
|
||||
row.insert("tex_in".to_string(), NodeValue::Texture(crate::handle::CHandle::null()));
|
||||
row.insert(
|
||||
"tex_in".to_string(),
|
||||
NodeValue::Texture(crate::handle::CHandle::null()),
|
||||
);
|
||||
row.insert("none_in".to_string(), NodeValue::None);
|
||||
let mut table = NodeValueTable::default();
|
||||
n.value(&core, &row, Rational::new(0, 1), &mut table);
|
||||
@@ -408,8 +430,14 @@ mod tests {
|
||||
let n = node();
|
||||
let core = NodeCore::new();
|
||||
let mut row = NodeValueRow::default();
|
||||
row.insert(SOURCE_CLIP.to_string(), NodeValue::Texture(crate::handle::CHandle::null()));
|
||||
row.insert(TEXTURE_INPUT.to_string(), NodeValue::Texture(crate::handle::CHandle::null()));
|
||||
row.insert(
|
||||
SOURCE_CLIP.to_string(),
|
||||
NodeValue::Texture(crate::handle::CHandle::null()),
|
||||
);
|
||||
row.insert(
|
||||
TEXTURE_INPUT.to_string(),
|
||||
NodeValue::Texture(crate::handle::CHandle::null()),
|
||||
);
|
||||
let mut table = NodeValueTable::default();
|
||||
n.value(&core, &row, Rational::new(0, 1), &mut table);
|
||||
assert!(matches!(
|
||||
@@ -426,7 +454,10 @@ mod tests {
|
||||
};
|
||||
let core = NodeCore::new();
|
||||
let mut row = NodeValueRow::default();
|
||||
row.insert(TEXTURE_INPUT.to_string(), NodeValue::Texture(crate::handle::CHandle::null()));
|
||||
row.insert(
|
||||
TEXTURE_INPUT.to_string(),
|
||||
NodeValue::Texture(crate::handle::CHandle::null()),
|
||||
);
|
||||
let mut table = NodeValueTable::default();
|
||||
n.value(&core, &row, Rational::new(0, 1), &mut table);
|
||||
assert!(table.is_empty());
|
||||
@@ -442,17 +473,20 @@ mod tests {
|
||||
sample_count: 3,
|
||||
// Planar layout: channel 0 plane [1, 2, 3], channel 1 plane
|
||||
// [4, 5, 6].
|
||||
data: vec![
|
||||
1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0,
|
||||
]
|
||||
.iter()
|
||||
.flat_map(|f| f.to_le_bytes())
|
||||
.collect(),
|
||||
data: vec![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0]
|
||||
.iter()
|
||||
.flat_map(|f| f.to_le_bytes())
|
||||
.collect(),
|
||||
};
|
||||
let mut row = NodeValueRow::default();
|
||||
row.insert("samples_in".to_string(), NodeValue::Samples(input));
|
||||
let mut output = crate::value::SampleBuffer::default();
|
||||
n.process_samples(&core, &row, TimeRange::new(Rational::new(0, 1), Rational::new(1, 1)), &mut output);
|
||||
n.process_samples(
|
||||
&core,
|
||||
&row,
|
||||
TimeRange::new(Rational::new(0, 1), Rational::new(1, 1)),
|
||||
&mut output,
|
||||
);
|
||||
assert!(output.is_allocated());
|
||||
assert_eq!(output.channels, 2);
|
||||
assert_eq!(output.sample_count, 3);
|
||||
@@ -473,9 +507,17 @@ mod tests {
|
||||
format: oakcore_rs::SampleFormat::F32,
|
||||
channels: 1,
|
||||
sample_count: 2,
|
||||
data: vec![1.0f32, 2.0].iter().flat_map(|f| f.to_le_bytes()).collect(),
|
||||
data: vec![1.0f32, 2.0]
|
||||
.iter()
|
||||
.flat_map(|f| f.to_le_bytes())
|
||||
.collect(),
|
||||
};
|
||||
n.process_samples(&core, &NodeValueRow::default(), TimeRange::new(Rational::new(0, 1), Rational::new(1, 1)), &mut output);
|
||||
n.process_samples(
|
||||
&core,
|
||||
&NodeValueRow::default(),
|
||||
TimeRange::new(Rational::new(0, 1), Rational::new(1, 1)),
|
||||
&mut output,
|
||||
);
|
||||
assert_eq!(output.sample_value(0, 0), 0.0);
|
||||
assert_eq!(output.sample_value(0, 1), 0.0);
|
||||
}
|
||||
|
||||
@@ -272,7 +272,10 @@ mod tests {
|
||||
#[test]
|
||||
fn input_names() {
|
||||
let n = PolygonGenerator;
|
||||
assert_eq!(n.input_name(super::super::generatorwithmerge::BASE_INPUT), "Base");
|
||||
assert_eq!(
|
||||
n.input_name(super::super::generatorwithmerge::BASE_INPUT),
|
||||
"Base"
|
||||
);
|
||||
assert_eq!(n.input_name(POINTS_INPUT), "Points");
|
||||
assert_eq!(n.input_name(COLOR_INPUT), "Color");
|
||||
}
|
||||
@@ -297,7 +300,10 @@ mod tests {
|
||||
core.get_input(COLOR_INPUT).unwrap().default,
|
||||
NodeValue::Color([1.0, 1.0, 1.0, 1.0])
|
||||
);
|
||||
assert_eq!(core.effect_input, super::super::generatorwithmerge::BASE_INPUT);
|
||||
assert_eq!(
|
||||
core.effect_input,
|
||||
super::super::generatorwithmerge::BASE_INPUT
|
||||
);
|
||||
assert_ne!(core.flags & crate::node::flags::VIDEO_EFFECT, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -319,10 +319,22 @@ mod tests {
|
||||
let n = ShapeNode;
|
||||
assert_eq!(n.input_name(TYPE_INPUT), "Type");
|
||||
assert_eq!(n.input_name(RADIUS_INPUT), "Radius");
|
||||
assert_eq!(n.input_name(super::super::generatorwithmerge::BASE_INPUT), "Base");
|
||||
assert_eq!(n.input_name(super::super::shapenodebase::POSITION_INPUT), "Position");
|
||||
assert_eq!(n.input_name(super::super::shapenodebase::SIZE_INPUT), "Size");
|
||||
assert_eq!(n.input_name(super::super::shapenodebase::COLOR_INPUT), "Color");
|
||||
assert_eq!(
|
||||
n.input_name(super::super::generatorwithmerge::BASE_INPUT),
|
||||
"Base"
|
||||
);
|
||||
assert_eq!(
|
||||
n.input_name(super::super::shapenodebase::POSITION_INPUT),
|
||||
"Position"
|
||||
);
|
||||
assert_eq!(
|
||||
n.input_name(super::super::shapenodebase::SIZE_INPUT),
|
||||
"Size"
|
||||
);
|
||||
assert_eq!(
|
||||
n.input_name(super::super::shapenodebase::COLOR_INPUT),
|
||||
"Color"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -337,14 +349,21 @@ mod tests {
|
||||
NodeValue::Float(20.0)
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(super::super::shapenodebase::SIZE_INPUT).unwrap().default,
|
||||
core.get_input(super::super::shapenodebase::SIZE_INPUT)
|
||||
.unwrap()
|
||||
.default,
|
||||
NodeValue::Vec2([100.0, 100.0])
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(super::super::shapenodebase::COLOR_INPUT).unwrap().default,
|
||||
core.get_input(super::super::shapenodebase::COLOR_INPUT)
|
||||
.unwrap()
|
||||
.default,
|
||||
NodeValue::Color([1.0, 0.0, 0.0, 1.0])
|
||||
);
|
||||
assert_eq!(core.effect_input, super::super::generatorwithmerge::BASE_INPUT);
|
||||
assert_eq!(
|
||||
core.effect_input,
|
||||
super::super::generatorwithmerge::BASE_INPUT
|
||||
);
|
||||
assert_ne!(core.flags & crate::node::flags::VIDEO_EFFECT, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,10 @@ impl ShapeNodeBase {
|
||||
/// signature. The property write and the gizmo point placements are
|
||||
/// therefore not representable here (`// CPP-PARITY:
|
||||
/// shapenodebase.cpp` `update_gizmo_positions`).
|
||||
pub fn update_gizmo_positions(core: &mut crate::node::NodeCore, row: &crate::value::NodeValueRow) {
|
||||
pub fn update_gizmo_positions(
|
||||
core: &mut crate::node::NodeCore,
|
||||
row: &crate::value::NodeValueRow,
|
||||
) {
|
||||
let _ = (core, row);
|
||||
}
|
||||
|
||||
@@ -130,6 +133,9 @@ mod tests {
|
||||
ShapeNodeBase::update_gizmo_positions(&mut core, &row);
|
||||
ShapeNodeBase::set_rect(&mut core, (0.0, 0.0, 100.0, 100.0));
|
||||
ShapeNodeBase::gizmo_drag_move(&mut core, 10.0, 20.0, 0);
|
||||
assert!(core.get_input(POSITION_INPUT).is_none(), "no inputs are added");
|
||||
assert!(
|
||||
core.get_input(POSITION_INPUT).is_none(),
|
||||
"no inputs are added"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +120,10 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
crate::value::ValueType::Color,
|
||||
crate::value::NodeValue::Color([1.0, 0.0, 0.0, 1.0]),
|
||||
);
|
||||
color.properties = vec![("view".to_string(), crate::value::NodeValue::Text("color".into()))];
|
||||
color.properties = vec![(
|
||||
"view".to_string(),
|
||||
crate::value::NodeValue::Text("color".into()),
|
||||
)];
|
||||
core.add_input(color);
|
||||
(core, Box::new(SolidGenerator))
|
||||
}
|
||||
@@ -142,7 +145,10 @@ mod tests {
|
||||
#[test]
|
||||
fn create_wires_inputs() {
|
||||
let (core, behavior) = create();
|
||||
assert_eq!(behavior.type_id(), "org.olivevideoeditor.Olive.solidgenerator");
|
||||
assert_eq!(
|
||||
behavior.type_id(),
|
||||
"org.olivevideoeditor.Olive.solidgenerator"
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(COLOR_INPUT).unwrap().default,
|
||||
NodeValue::Color([1.0, 0.0, 0.0, 1.0])
|
||||
|
||||
@@ -260,7 +260,10 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
crate::value::NodeValue::Float(1.0),
|
||||
);
|
||||
opacity.properties = vec![
|
||||
("view".to_string(), crate::value::NodeValue::Text("percentage".into())),
|
||||
(
|
||||
"view".to_string(),
|
||||
crate::value::NodeValue::Text("percentage".into()),
|
||||
),
|
||||
("min".to_string(), crate::value::NodeValue::Float(0.0)),
|
||||
("max".to_string(), crate::value::NodeValue::Float(1.0)),
|
||||
];
|
||||
|
||||
@@ -142,10 +142,7 @@ pub type TextRenderBackend =
|
||||
/// facade installs a text engine. Setting a hook to `None` uninstalls
|
||||
/// it (the C++ global is a plain function pointer, assignable any
|
||||
/// number of times; a `Mutex` keeps the tests able to reset it).
|
||||
pub fn set_text_backends(
|
||||
measure: Option<TextMeasureBackend>,
|
||||
render: Option<TextRenderBackend>,
|
||||
) {
|
||||
pub fn set_text_backends(measure: Option<TextMeasureBackend>, render: Option<TextRenderBackend>) {
|
||||
*MEASURE.lock().unwrap() = measure;
|
||||
*RENDER.lock().unwrap() = render;
|
||||
}
|
||||
@@ -198,15 +195,19 @@ mod tests {
|
||||
}
|
||||
}
|
||||
set_text_backends(Some(measure), Some(render));
|
||||
assert_eq!(text_measure_backend().unwrap()(&TextLayoutRequest {
|
||||
text: String::new(),
|
||||
mode: TextLayoutMode::PlainText,
|
||||
font_family: String::new(),
|
||||
font_size_pt: 0.0,
|
||||
dots_per_meter: 0,
|
||||
wrap_width: 0.0,
|
||||
center_horizontally: false,
|
||||
}).width, 12.0);
|
||||
assert_eq!(
|
||||
text_measure_backend().unwrap()(&TextLayoutRequest {
|
||||
text: String::new(),
|
||||
mode: TextLayoutMode::PlainText,
|
||||
font_family: String::new(),
|
||||
font_size_pt: 0.0,
|
||||
dots_per_meter: 0,
|
||||
wrap_width: 0.0,
|
||||
center_horizontally: false,
|
||||
})
|
||||
.width,
|
||||
12.0
|
||||
);
|
||||
assert_eq!(
|
||||
text_measure_backend().unwrap()(&TextLayoutRequest {
|
||||
text: String::new(),
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -32,9 +32,7 @@ use crate::node::{Category, NodeBehavior, NodeCore};
|
||||
use crate::value::{NodeValue, NodeValueRow, NodeValueTable};
|
||||
use oakcore_rs::Rational;
|
||||
|
||||
use super::textbackend::{
|
||||
TextLayoutMode, TextLayoutRequest, TextLayoutSize, TextRenderTransform,
|
||||
};
|
||||
use super::textbackend::{TextLayoutMode, TextLayoutRequest, TextLayoutSize, TextRenderTransform};
|
||||
|
||||
/// Text input id (C++ `k_text_input`). Type: text; default
|
||||
/// `"Sample Text"`.
|
||||
@@ -103,13 +101,19 @@ impl TextGeneratorV2 {
|
||||
} else {
|
||||
text
|
||||
};
|
||||
let size = row.get(crate::nodes::shapenodebase::SIZE_INPUT).map(to_vec2).unwrap_or([0.0, 0.0]);
|
||||
let size = row
|
||||
.get(crate::nodes::shapenodebase::SIZE_INPUT)
|
||||
.map(to_vec2)
|
||||
.unwrap_or([0.0, 0.0]);
|
||||
|
||||
TextLayoutRequest {
|
||||
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: 2835,
|
||||
wrap_width: size[0],
|
||||
center_horizontally: false,
|
||||
@@ -120,7 +124,12 @@ impl TextGeneratorV2 {
|
||||
/// position re-centered into frame space —
|
||||
/// `pos - size/2 + frame/2` (the frame halves are integer division in
|
||||
/// C++).
|
||||
pub fn base_offset(pos: [f64; 2], size: [f64; 2], frame_width: i32, frame_height: i32) -> (f64, f64) {
|
||||
pub fn base_offset(
|
||||
pos: [f64; 2],
|
||||
size: [f64; 2],
|
||||
frame_width: i32,
|
||||
frame_height: i32,
|
||||
) -> (f64, f64) {
|
||||
(
|
||||
pos[0] - size[0] / 2.0 + (frame_width / 2) as f64,
|
||||
pos[1] - size[1] / 2.0 + (frame_height / 2) as f64,
|
||||
@@ -131,7 +140,12 @@ impl TextGeneratorV2 {
|
||||
/// offset plus the vertical alignment delta — top: none; center:
|
||||
/// `size.y/2 - doc_height/2` (the halving is integer on the
|
||||
/// `int(doc.height)`); bottom: `size.y - doc_height`.
|
||||
pub fn draw_offset(valign: i32, base: (f64, f64), size: [f64; 2], doc_height: i32) -> (f64, f64) {
|
||||
pub fn draw_offset(
|
||||
valign: i32,
|
||||
base: (f64, f64),
|
||||
size: [f64; 2],
|
||||
doc_height: i32,
|
||||
) -> (f64, f64) {
|
||||
let (dx, mut dy) = base;
|
||||
match valign {
|
||||
// k_vertical_align_top: do nothing.
|
||||
@@ -149,7 +163,12 @@ impl TextGeneratorV2 {
|
||||
/// scale, the draw offset, and the clip rect at the base offset
|
||||
/// covering the shape size (set before the vertical-alignment
|
||||
/// translate in the C++).
|
||||
pub fn render_transform(scale: f64, draw: (f64, f64), base: (f64, f64), size: [f64; 2]) -> TextRenderTransform {
|
||||
pub fn render_transform(
|
||||
scale: f64,
|
||||
draw: (f64, f64),
|
||||
base: (f64, f64),
|
||||
size: [f64; 2],
|
||||
) -> TextRenderTransform {
|
||||
TextRenderTransform {
|
||||
scale,
|
||||
draw_offset_x: draw.0,
|
||||
@@ -181,10 +200,19 @@ impl TextGeneratorV2 {
|
||||
Some(measure) => measure(&req),
|
||||
None => TextLayoutSize::default(),
|
||||
};
|
||||
let size = row.get(crate::nodes::shapenodebase::SIZE_INPUT).map(to_vec2).unwrap_or([0.0, 0.0]);
|
||||
let pos = row.get(crate::nodes::shapenodebase::POSITION_INPUT).map(to_vec2).unwrap_or([0.0, 0.0]);
|
||||
let size = row
|
||||
.get(crate::nodes::shapenodebase::SIZE_INPUT)
|
||||
.map(to_vec2)
|
||||
.unwrap_or([0.0, 0.0]);
|
||||
let pos = row
|
||||
.get(crate::nodes::shapenodebase::POSITION_INPUT)
|
||||
.map(to_vec2)
|
||||
.unwrap_or([0.0, 0.0]);
|
||||
let base = Self::base_offset(pos, size, frame_width, frame_height);
|
||||
let valign = row.get(V_ALIGN_INPUT).map(NodeValue::to_double).unwrap_or(0.0) as i32;
|
||||
let valign = row
|
||||
.get(V_ALIGN_INPUT)
|
||||
.map(NodeValue::to_double)
|
||||
.unwrap_or(0.0) as i32;
|
||||
let draw = Self::draw_offset(valign, base, size, doc.height as i32);
|
||||
(req, doc, base, draw)
|
||||
}
|
||||
@@ -239,11 +267,18 @@ impl NodeBehavior for TextGeneratorV2 {
|
||||
/// resolved via [`NodeBehavior::generate_frame`]; the f32 forcing is
|
||||
/// renderer-side and has no representation here
|
||||
/// (`// CPP-PARITY: textv2.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,
|
||||
@@ -322,10 +357,7 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
crate::value::ValueType::Vec2,
|
||||
NodeValue::Vec2([100.0, 100.0]),
|
||||
);
|
||||
size.properties = vec![(
|
||||
"min".to_string(),
|
||||
NodeValue::Vec2([0.0, 0.0]),
|
||||
)];
|
||||
size.properties = vec![("min".to_string(), NodeValue::Vec2([0.0, 0.0]))];
|
||||
core.add_input(size);
|
||||
core.add_input(crate::input::Input::new(
|
||||
crate::nodes::shapenodebase::COLOR_INPUT,
|
||||
@@ -403,10 +435,22 @@ mod tests {
|
||||
assert_eq!(n.input_name(V_ALIGN_INPUT), "Vertical Align");
|
||||
assert_eq!(n.input_name(FONT_INPUT), "Font");
|
||||
assert_eq!(n.input_name(FONT_SIZE_INPUT), "Font Size");
|
||||
assert_eq!(n.input_name(crate::nodes::generatorwithmerge::BASE_INPUT), "Base");
|
||||
assert_eq!(n.input_name(crate::nodes::shapenodebase::POSITION_INPUT), "Position");
|
||||
assert_eq!(n.input_name(crate::nodes::shapenodebase::SIZE_INPUT), "Size");
|
||||
assert_eq!(n.input_name(crate::nodes::shapenodebase::COLOR_INPUT), "Color");
|
||||
assert_eq!(
|
||||
n.input_name(crate::nodes::generatorwithmerge::BASE_INPUT),
|
||||
"Base"
|
||||
);
|
||||
assert_eq!(
|
||||
n.input_name(crate::nodes::shapenodebase::POSITION_INPUT),
|
||||
"Position"
|
||||
);
|
||||
assert_eq!(
|
||||
n.input_name(crate::nodes::shapenodebase::SIZE_INPUT),
|
||||
"Size"
|
||||
);
|
||||
assert_eq!(
|
||||
n.input_name(crate::nodes::shapenodebase::COLOR_INPUT),
|
||||
"Color"
|
||||
);
|
||||
assert_eq!(n.input_name("other_in"), "other_in");
|
||||
}
|
||||
|
||||
@@ -414,9 +458,18 @@ mod tests {
|
||||
fn create_wires_inherited_and_own_inputs() {
|
||||
let (core, behavior) = create();
|
||||
assert_eq!(behavior.type_id(), "org.olivevideoeditor.Olive.text2");
|
||||
assert_eq!(core.get_input(TEXT_INPUT).unwrap().value_type, ValueType::Text);
|
||||
assert_eq!(core.get_input(V_ALIGN_INPUT).unwrap().default, NodeValue::Combo(0));
|
||||
assert_eq!(core.effect_input, crate::nodes::generatorwithmerge::BASE_INPUT);
|
||||
assert_eq!(
|
||||
core.get_input(TEXT_INPUT).unwrap().value_type,
|
||||
ValueType::Text
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(V_ALIGN_INPUT).unwrap().default,
|
||||
NodeValue::Combo(0)
|
||||
);
|
||||
assert_eq!(
|
||||
core.effect_input,
|
||||
crate::nodes::generatorwithmerge::BASE_INPUT
|
||||
);
|
||||
assert_ne!(core.flags & crate::node::flags::VIDEO_EFFECT, 0);
|
||||
assert_ne!(core.flags & crate::node::flags::DONT_SHOW_IN_CREATE_MENU, 0);
|
||||
// Inherited standard-value overrides.
|
||||
@@ -436,7 +489,10 @@ mod tests {
|
||||
row.insert(TEXT_INPUT.to_string(), NodeValue::Text("Hi".to_string()));
|
||||
row.insert(FONT_SIZE_INPUT.to_string(), NodeValue::Float(36.0));
|
||||
row.insert(HTML_INPUT.to_string(), NodeValue::Boolean(false));
|
||||
row.insert(crate::nodes::shapenodebase::SIZE_INPUT.to_string(), NodeValue::Vec2([400.0, 300.0]));
|
||||
row.insert(
|
||||
crate::nodes::shapenodebase::SIZE_INPUT.to_string(),
|
||||
NodeValue::Vec2([400.0, 300.0]),
|
||||
);
|
||||
let req = TextGeneratorV2::layout_request(&row);
|
||||
assert_eq!(req.text, "Hi");
|
||||
assert_eq!(req.font_size_pt, 36.0);
|
||||
@@ -450,7 +506,10 @@ mod tests {
|
||||
let mut row = NodeValueRow::default();
|
||||
row.insert(TEXT_INPUT.to_string(), NodeValue::Text("a\nb".to_string()));
|
||||
row.insert(HTML_INPUT.to_string(), NodeValue::Boolean(true));
|
||||
row.insert(crate::nodes::shapenodebase::SIZE_INPUT.to_string(), NodeValue::Vec2([100.0, 100.0]));
|
||||
row.insert(
|
||||
crate::nodes::shapenodebase::SIZE_INPUT.to_string(),
|
||||
NodeValue::Vec2([100.0, 100.0]),
|
||||
);
|
||||
let req = TextGeneratorV2::layout_request(&row);
|
||||
assert_eq!(req.text, "a<br>b");
|
||||
assert_eq!(req.mode, TextLayoutMode::Html);
|
||||
@@ -496,8 +555,14 @@ mod tests {
|
||||
crate::nodes::textbackend::set_text_backends(None, None);
|
||||
let mut row = NodeValueRow::default();
|
||||
row.insert(TEXT_INPUT.to_string(), NodeValue::Text("Hi".to_string()));
|
||||
row.insert(crate::nodes::shapenodebase::POSITION_INPUT.to_string(), NodeValue::Vec2([0.0, 0.0]));
|
||||
row.insert(crate::nodes::shapenodebase::SIZE_INPUT.to_string(), NodeValue::Vec2([400.0, 300.0]));
|
||||
row.insert(
|
||||
crate::nodes::shapenodebase::POSITION_INPUT.to_string(),
|
||||
NodeValue::Vec2([0.0, 0.0]),
|
||||
);
|
||||
row.insert(
|
||||
crate::nodes::shapenodebase::SIZE_INPUT.to_string(),
|
||||
NodeValue::Vec2([400.0, 300.0]),
|
||||
);
|
||||
row.insert(V_ALIGN_INPUT.to_string(), NodeValue::Combo(1));
|
||||
let (_req, doc, base, draw) = TextGeneratorV2::measure_and_layout(&row, 1920, 1080);
|
||||
assert_eq!(doc.width, 0.0);
|
||||
|
||||
@@ -79,7 +79,8 @@ impl VerticalAlignment {
|
||||
|
||||
/// The C++ `k_input_flag_static` mask: not-connectable +
|
||||
/// not-keyframable.
|
||||
const STATIC_FLAGS: u32 = crate::input::flags::NOT_CONNECTABLE | crate::input::flags::NOT_KEYFRAMABLE;
|
||||
const STATIC_FLAGS: u32 =
|
||||
crate::input::flags::NOT_CONNECTABLE | crate::input::flags::NOT_KEYFRAMABLE;
|
||||
|
||||
/// Rich text generator v3 (the current "Text" node). Inherits
|
||||
/// position/size/color inputs and the polygon gizmo from the shape base
|
||||
@@ -148,7 +149,10 @@ impl TextGeneratorV3 {
|
||||
/// The current alignment (C++ `get_vertical_alignment()`): the
|
||||
/// `valign_in` standard value as a [`VerticalAlignment`].
|
||||
pub fn vertical_alignment(core: &NodeCore) -> VerticalAlignment {
|
||||
VerticalAlignment::from_int(core.standard_value(VERTICAL_ALIGNMENT_INPUT, -1).to_double() as i32)
|
||||
VerticalAlignment::from_int(
|
||||
core.standard_value(VERTICAL_ALIGNMENT_INPUT, -1)
|
||||
.to_double() as i32,
|
||||
)
|
||||
}
|
||||
|
||||
/// Expand `%N` placeholders with args (C++ `format_string()`):
|
||||
@@ -282,7 +286,13 @@ impl NodeBehavior for TextGeneratorV3 {
|
||||
/// value when present (a per-element array model is deferred), so
|
||||
/// `%N` expansion is exercised directly via [`Self::format_string`]
|
||||
/// (`// CPP-PARITY: textv3.cpp` `value()`).
|
||||
fn value(&self, core: &NodeCore, inputs: &NodeValueRow, time: Rational, table: &mut NodeValueTable) {
|
||||
fn value(
|
||||
&self,
|
||||
core: &NodeCore,
|
||||
inputs: &NodeValueRow,
|
||||
time: Rational,
|
||||
table: &mut NodeValueTable,
|
||||
) {
|
||||
let text_val = inputs
|
||||
.get(TEXT_INPUT)
|
||||
.cloned()
|
||||
@@ -313,7 +323,9 @@ impl NodeBehavior for TextGeneratorV3 {
|
||||
crate::handle::CHandle::null(),
|
||||
table,
|
||||
);
|
||||
} else if let Some(base @ NodeValue::Texture(_)) = inputs.get(crate::nodes::generatorwithmerge::BASE_INPUT) {
|
||||
} else if let Some(base @ NodeValue::Texture(_)) =
|
||||
inputs.get(crate::nodes::generatorwithmerge::BASE_INPUT)
|
||||
{
|
||||
table.push(base.value_type(), base.clone(), None);
|
||||
}
|
||||
}
|
||||
@@ -395,7 +407,10 @@ impl TextGeneratorV3 {
|
||||
/// size X. Font family/size come from the markup; the backend defaults
|
||||
/// are used when absent.
|
||||
pub fn layout_request(row: &NodeValueRow) -> TextLayoutRequest {
|
||||
let size = row.get(crate::nodes::shapenodebase::SIZE_INPUT).map(to_vec2).unwrap_or([0.0, 0.0]);
|
||||
let size = row
|
||||
.get(crate::nodes::shapenodebase::SIZE_INPUT)
|
||||
.map(to_vec2)
|
||||
.unwrap_or([0.0, 0.0]);
|
||||
TextLayoutRequest {
|
||||
text: row.get(TEXT_INPUT).map(to_text).unwrap_or_else(String::new),
|
||||
mode: TextLayoutMode::OliveHtml,
|
||||
@@ -410,7 +425,12 @@ impl TextGeneratorV3 {
|
||||
/// The C++ base offset (textv3.cpp `generate_frame()`): the shape
|
||||
/// position re-centered into frame space — `pos - size/2 + frame/2`
|
||||
/// (the frame halves are integer division in C++).
|
||||
pub fn base_offset(pos: [f64; 2], size: [f64; 2], frame_width: i32, frame_height: i32) -> (f64, f64) {
|
||||
pub fn base_offset(
|
||||
pos: [f64; 2],
|
||||
size: [f64; 2],
|
||||
frame_width: i32,
|
||||
frame_height: i32,
|
||||
) -> (f64, f64) {
|
||||
(
|
||||
pos[0] - size[0] / 2.0 + (frame_width / 2) as f64,
|
||||
pos[1] - size[1] / 2.0 + (frame_height / 2) as f64,
|
||||
@@ -421,7 +441,12 @@ impl TextGeneratorV3 {
|
||||
/// offset plus the vertical-alignment delta — top: none; middle:
|
||||
/// `size.y/2 - doc.height/2`; bottom: `size.y - doc.height` (all
|
||||
/// double math, unlike the integer halving in v2).
|
||||
pub fn draw_offset(align: VerticalAlignment, base: (f64, f64), size: [f64; 2], doc_height: f64) -> (f64, f64) {
|
||||
pub fn draw_offset(
|
||||
align: VerticalAlignment,
|
||||
base: (f64, f64),
|
||||
size: [f64; 2],
|
||||
doc_height: f64,
|
||||
) -> (f64, f64) {
|
||||
let (dx, mut dy) = base;
|
||||
match align {
|
||||
VerticalAlignment::Top => {}
|
||||
@@ -435,7 +460,12 @@ impl TextGeneratorV3 {
|
||||
/// scale, the draw offset, and the clip rect at the base offset
|
||||
/// covering the shape size (set before the vertical-alignment
|
||||
/// translate in the C++).
|
||||
pub fn render_transform(scale: f64, draw: (f64, f64), base: (f64, f64), size: [f64; 2]) -> TextRenderTransform {
|
||||
pub fn render_transform(
|
||||
scale: f64,
|
||||
draw: (f64, f64),
|
||||
base: (f64, f64),
|
||||
size: [f64; 2],
|
||||
) -> TextRenderTransform {
|
||||
TextRenderTransform {
|
||||
scale,
|
||||
draw_offset_x: draw.0,
|
||||
@@ -511,9 +541,7 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
let mut text = crate::input::Input::new(
|
||||
TEXT_INPUT,
|
||||
crate::value::ValueType::Text,
|
||||
NodeValue::Text(
|
||||
"<p style='font-size: 72pt; color: white;'>Sample Text</p>".to_string(),
|
||||
),
|
||||
NodeValue::Text("<p style='font-size: 72pt; color: white;'>Sample Text</p>".to_string()),
|
||||
);
|
||||
text.properties = vec![("vieweronly".to_string(), NodeValue::Boolean(true))];
|
||||
core.add_input(text);
|
||||
@@ -550,9 +578,12 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
NodeValue::Vec2([400.0, 300.0]),
|
||||
);
|
||||
|
||||
(core, Box::new(TextGeneratorV3 {
|
||||
dont_emit_valign: false,
|
||||
}))
|
||||
(
|
||||
core,
|
||||
Box::new(TextGeneratorV3 {
|
||||
dont_emit_valign: false,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
/// Register this node type (C++ `k_text_generator_v3` in
|
||||
@@ -575,13 +606,24 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn input_names() {
|
||||
let n = TextGeneratorV3 { dont_emit_valign: false };
|
||||
let n = TextGeneratorV3 {
|
||||
dont_emit_valign: false,
|
||||
};
|
||||
assert_eq!(n.input_name(TEXT_INPUT), "Text");
|
||||
assert_eq!(n.input_name(VERTICAL_ALIGNMENT_INPUT), "Vertical Alignment");
|
||||
assert_eq!(n.input_name(ARGS_INPUT), "Arguments");
|
||||
assert_eq!(n.input_name(crate::nodes::generatorwithmerge::BASE_INPUT), "Base");
|
||||
assert_eq!(n.input_name(crate::nodes::shapenodebase::POSITION_INPUT), "Position");
|
||||
assert_eq!(n.input_name(crate::nodes::shapenodebase::SIZE_INPUT), "Size");
|
||||
assert_eq!(
|
||||
n.input_name(crate::nodes::generatorwithmerge::BASE_INPUT),
|
||||
"Base"
|
||||
);
|
||||
assert_eq!(
|
||||
n.input_name(crate::nodes::shapenodebase::POSITION_INPUT),
|
||||
"Position"
|
||||
);
|
||||
assert_eq!(
|
||||
n.input_name(crate::nodes::shapenodebase::SIZE_INPUT),
|
||||
"Size"
|
||||
);
|
||||
// The hidden use_args_in input has no display name override.
|
||||
assert_eq!(n.input_name(USE_ARGS_INPUT), USE_ARGS_INPUT);
|
||||
}
|
||||
@@ -590,14 +632,16 @@ mod tests {
|
||||
fn create_wires_inherited_and_own_inputs() {
|
||||
let (core, behavior) = create();
|
||||
assert_eq!(behavior.type_id(), "org.olivevideoeditor.Olive.text3");
|
||||
assert_eq!(core.get_input(TEXT_INPUT).unwrap().value_type, ValueType::Text);
|
||||
assert!(
|
||||
core.get_input(TEXT_INPUT)
|
||||
.unwrap()
|
||||
.properties
|
||||
.iter()
|
||||
.any(|(k, v)| k == "vieweronly" && v == &NodeValue::Boolean(true))
|
||||
assert_eq!(
|
||||
core.get_input(TEXT_INPUT).unwrap().value_type,
|
||||
ValueType::Text
|
||||
);
|
||||
assert!(core
|
||||
.get_input(TEXT_INPUT)
|
||||
.unwrap()
|
||||
.properties
|
||||
.iter()
|
||||
.any(|(k, v)| k == "vieweronly" && v == &NodeValue::Boolean(true)));
|
||||
let valign = core.get_input(VERTICAL_ALIGNMENT_INPUT).unwrap();
|
||||
assert_ne!(valign.flags & crate::input::flags::HIDDEN, 0);
|
||||
assert_ne!(valign.flags & crate::input::flags::NOT_CONNECTABLE, 0);
|
||||
@@ -606,35 +650,62 @@ mod tests {
|
||||
assert_eq!(use_args.default, NodeValue::Boolean(true));
|
||||
let args = core.get_input(ARGS_INPUT).unwrap();
|
||||
assert_ne!(args.flags & crate::input::flags::ARRAY, 0);
|
||||
assert!(args.properties.iter().any(|(k, v)| k == "arraystart" && v == &NodeValue::Int(1)));
|
||||
assert!(args
|
||||
.properties
|
||||
.iter()
|
||||
.any(|(k, v)| k == "arraystart" && v == &NodeValue::Int(1)));
|
||||
// No color input (ShapeNodeBase(false)).
|
||||
assert!(core.get_input(crate::nodes::shapenodebase::COLOR_INPUT).is_none());
|
||||
assert!(core
|
||||
.get_input(crate::nodes::shapenodebase::COLOR_INPUT)
|
||||
.is_none());
|
||||
assert_eq!(
|
||||
core.standard_value(crate::nodes::shapenodebase::SIZE_INPUT, -1),
|
||||
NodeValue::Vec2([400.0, 300.0])
|
||||
);
|
||||
assert_eq!(core.effect_input, crate::nodes::generatorwithmerge::BASE_INPUT);
|
||||
assert_eq!(
|
||||
core.effect_input,
|
||||
crate::nodes::generatorwithmerge::BASE_INPUT
|
||||
);
|
||||
// v3 is shown in the create menu (no DONT_SHOW_IN_CREATE_MENU flag).
|
||||
assert_eq!(core.flags & crate::node::flags::DONT_SHOW_IN_CREATE_MENU, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alignment_round_trip() {
|
||||
for v in [VerticalAlignment::Top, VerticalAlignment::Middle, VerticalAlignment::Bottom] {
|
||||
for v in [
|
||||
VerticalAlignment::Top,
|
||||
VerticalAlignment::Middle,
|
||||
VerticalAlignment::Bottom,
|
||||
] {
|
||||
let gizmo = TextGeneratorV3::get_gizmo_alignment_from_ours(v);
|
||||
assert_eq!(TextGeneratorV3::get_our_alignment_from_gizmos(gizmo), v);
|
||||
}
|
||||
assert_eq!(TextGeneratorV3::get_gizmo_alignment_from_ours(VerticalAlignment::Top), 0);
|
||||
assert_eq!(TextGeneratorV3::get_gizmo_alignment_from_ours(VerticalAlignment::Middle), 2);
|
||||
assert_eq!(TextGeneratorV3::get_gizmo_alignment_from_ours(VerticalAlignment::Bottom), 1);
|
||||
assert_eq!(
|
||||
TextGeneratorV3::get_gizmo_alignment_from_ours(VerticalAlignment::Top),
|
||||
0
|
||||
);
|
||||
assert_eq!(
|
||||
TextGeneratorV3::get_gizmo_alignment_from_ours(VerticalAlignment::Middle),
|
||||
2
|
||||
);
|
||||
assert_eq!(
|
||||
TextGeneratorV3::get_gizmo_alignment_from_ours(VerticalAlignment::Bottom),
|
||||
1
|
||||
);
|
||||
// Unknown gizmo values map to Top.
|
||||
assert_eq!(TextGeneratorV3::get_our_alignment_from_gizmos(99), VerticalAlignment::Top);
|
||||
assert_eq!(
|
||||
TextGeneratorV3::get_our_alignment_from_gizmos(99),
|
||||
VerticalAlignment::Top
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_string_expands_args() {
|
||||
let args = vec!["foo".to_string(), "bar".to_string()];
|
||||
assert_eq!(TextGeneratorV3::format_string("hello %1", &args), "hello foo");
|
||||
assert_eq!(
|
||||
TextGeneratorV3::format_string("hello %1", &args),
|
||||
"hello foo"
|
||||
);
|
||||
assert_eq!(TextGeneratorV3::format_string("%2 %1", &args), "bar foo");
|
||||
// Out of range expands to nothing.
|
||||
assert_eq!(TextGeneratorV3::format_string("[%3]", &args), "[]");
|
||||
@@ -655,7 +726,10 @@ mod tests {
|
||||
#[test]
|
||||
fn format_string_out_of_int_range_fails_to_zero() {
|
||||
let args = vec!["foo".to_string()];
|
||||
assert_eq!(TextGeneratorV3::format_string("%99999999999999999999", &args), "");
|
||||
assert_eq!(
|
||||
TextGeneratorV3::format_string("%99999999999999999999", &args),
|
||||
""
|
||||
);
|
||||
assert_eq!(TextGeneratorV3::format_string("%2147483648", &args), "");
|
||||
assert_eq!(TextGeneratorV3::format_string("%2147483647", &args), "");
|
||||
}
|
||||
@@ -671,8 +745,14 @@ mod tests {
|
||||
#[test]
|
||||
fn layout_request_uses_olive_html_and_96dpi() {
|
||||
let mut row = NodeValueRow::default();
|
||||
row.insert(TEXT_INPUT.to_string(), NodeValue::Text("<p>Hi</p>".to_string()));
|
||||
row.insert(crate::nodes::shapenodebase::SIZE_INPUT.to_string(), NodeValue::Vec2([400.0, 300.0]));
|
||||
row.insert(
|
||||
TEXT_INPUT.to_string(),
|
||||
NodeValue::Text("<p>Hi</p>".to_string()),
|
||||
);
|
||||
row.insert(
|
||||
crate::nodes::shapenodebase::SIZE_INPUT.to_string(),
|
||||
NodeValue::Vec2([400.0, 300.0]),
|
||||
);
|
||||
let req = TextGeneratorV3::layout_request(&row);
|
||||
assert_eq!(req.text, "<p>Hi</p>");
|
||||
assert_eq!(req.mode, TextLayoutMode::OliveHtml);
|
||||
@@ -704,7 +784,10 @@ mod tests {
|
||||
fn measure_without_backend_returns_zero_size() {
|
||||
crate::nodes::textbackend::set_text_backends(None, None);
|
||||
let mut row = NodeValueRow::default();
|
||||
row.insert(TEXT_INPUT.to_string(), NodeValue::Text("<p>Hi</p>".to_string()));
|
||||
row.insert(
|
||||
TEXT_INPUT.to_string(),
|
||||
NodeValue::Text("<p>Hi</p>".to_string()),
|
||||
);
|
||||
let (_req, doc) = TextGeneratorV3::measure_and_layout(&row);
|
||||
assert_eq!(doc.width, 0.0);
|
||||
assert_eq!(doc.height, 0.0);
|
||||
@@ -714,7 +797,10 @@ mod tests {
|
||||
fn value_pushes_job_when_text_nonempty() {
|
||||
let (core, behavior) = create();
|
||||
let mut row = NodeValueRow::default();
|
||||
row.insert(TEXT_INPUT.to_string(), NodeValue::Text("<p>Hi</p>".to_string()));
|
||||
row.insert(
|
||||
TEXT_INPUT.to_string(),
|
||||
NodeValue::Text("<p>Hi</p>".to_string()),
|
||||
);
|
||||
row.insert(USE_ARGS_INPUT.to_string(), NodeValue::Boolean(false));
|
||||
let mut table = NodeValueTable::default();
|
||||
behavior.value(&core, &row, Rational::new(0, 1), &mut table);
|
||||
@@ -728,7 +814,10 @@ mod tests {
|
||||
fn value_expands_args_from_row() {
|
||||
let (core, behavior) = create();
|
||||
let mut row = NodeValueRow::default();
|
||||
row.insert(TEXT_INPUT.to_string(), NodeValue::Text("Hello %1".to_string()));
|
||||
row.insert(
|
||||
TEXT_INPUT.to_string(),
|
||||
NodeValue::Text("Hello %1".to_string()),
|
||||
);
|
||||
row.insert(USE_ARGS_INPUT.to_string(), NodeValue::Boolean(true));
|
||||
row.insert(ARGS_INPUT.to_string(), NodeValue::Text("World".to_string()));
|
||||
let mut table = NodeValueTable::default();
|
||||
|
||||
@@ -228,7 +228,10 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
|
||||
let amount_props = vec![
|
||||
("min".to_string(), crate::value::NodeValue::Float(0.0)),
|
||||
("view".to_string(), crate::value::NodeValue::Text("percentage".into())),
|
||||
(
|
||||
"view".to_string(),
|
||||
crate::value::NodeValue::Text("percentage".into()),
|
||||
),
|
||||
];
|
||||
let mut shadows_amount = crate::input::Input::new(
|
||||
SHADOWS_AMOUNT_INPUT,
|
||||
@@ -291,18 +294,35 @@ mod tests {
|
||||
#[test]
|
||||
fn create_wires_inputs_flags_and_properties() {
|
||||
let (core, behavior) = create();
|
||||
assert_eq!(behavior.type_id(), "org.olivevideoeditor.Olive.threewaycolor");
|
||||
assert_eq!(
|
||||
behavior.type_id(),
|
||||
"org.olivevideoeditor.Olive.threewaycolor"
|
||||
);
|
||||
let tex = core.get_input(TEXTURE_INPUT).unwrap();
|
||||
assert_ne!(tex.flags & crate::input::flags::NOT_KEYFRAMABLE, 0);
|
||||
let neutral = NodeValue::Color([0.5, 0.5, 0.5, 1.0]);
|
||||
for id in [SHADOWS_COLOR_INPUT, MIDTONES_COLOR_INPUT, HIGHLIGHTS_COLOR_INPUT] {
|
||||
for id in [
|
||||
SHADOWS_COLOR_INPUT,
|
||||
MIDTONES_COLOR_INPUT,
|
||||
HIGHLIGHTS_COLOR_INPUT,
|
||||
] {
|
||||
assert_eq!(core.get_input(id).unwrap().default, neutral);
|
||||
}
|
||||
for id in [SHADOWS_AMOUNT_INPUT, MIDTONES_AMOUNT_INPUT, HIGHLIGHTS_AMOUNT_INPUT] {
|
||||
for id in [
|
||||
SHADOWS_AMOUNT_INPUT,
|
||||
MIDTONES_AMOUNT_INPUT,
|
||||
HIGHLIGHTS_AMOUNT_INPUT,
|
||||
] {
|
||||
let input = core.get_input(id).unwrap();
|
||||
assert_eq!(input.default, NodeValue::Float(1.0));
|
||||
assert!(input.properties.iter().any(|(k, v)| k == "min" && *v == NodeValue::Float(0.0)));
|
||||
assert!(input.properties.iter().any(|(k, v)| k == "view" && *v == NodeValue::Text("percentage".into())));
|
||||
assert!(input
|
||||
.properties
|
||||
.iter()
|
||||
.any(|(k, v)| k == "min" && *v == NodeValue::Float(0.0)));
|
||||
assert!(input
|
||||
.properties
|
||||
.iter()
|
||||
.any(|(k, v)| k == "view" && *v == NodeValue::Text("percentage".into())));
|
||||
}
|
||||
assert_eq!(core.effect_input, TEXTURE_INPUT);
|
||||
assert_ne!(core.flags & crate::node::flags::VIDEO_EFFECT, 0);
|
||||
@@ -318,7 +338,12 @@ mod tests {
|
||||
fn value_no_texture_pushes_nothing() {
|
||||
let (core, behavior) = create();
|
||||
let mut table = NodeValueTable::default();
|
||||
behavior.value(&core, &crate::value::NodeValueRow::default(), Rational::new(0, 1), &mut table);
|
||||
behavior.value(
|
||||
&core,
|
||||
&crate::value::NodeValueRow::default(),
|
||||
Rational::new(0, 1),
|
||||
&mut table,
|
||||
);
|
||||
assert!(table.is_empty());
|
||||
}
|
||||
|
||||
|
||||
@@ -296,7 +296,10 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
);
|
||||
scale.properties = vec![
|
||||
("min".to_string(), crate::value::NodeValue::Float(0.0)),
|
||||
("view".to_string(), crate::value::NodeValue::Text("percentage".into())),
|
||||
(
|
||||
"view".to_string(),
|
||||
crate::value::NodeValue::Text("percentage".into()),
|
||||
),
|
||||
];
|
||||
core.add_input(scale);
|
||||
|
||||
|
||||
@@ -121,7 +121,11 @@ fn format_date_time(tm: &Tm, ms: i32, format: &str) -> String {
|
||||
}
|
||||
'M' => {
|
||||
let mon = tm.tm_mon + 1;
|
||||
let s = if run >= 2 { format!("{:02}", mon) } else { format!("{}", mon) };
|
||||
let s = if run >= 2 {
|
||||
format!("{:02}", mon)
|
||||
} else {
|
||||
format!("{}", mon)
|
||||
};
|
||||
out.push_str(&s);
|
||||
}
|
||||
'y' => {
|
||||
@@ -148,7 +152,11 @@ fn format_date_time(tm: &Tm, ms: i32, format: &str) -> String {
|
||||
hour = 12;
|
||||
}
|
||||
}
|
||||
let s = if run >= 2 { format!("{:02}", hour) } else { format!("{}", hour) };
|
||||
let s = if run >= 2 {
|
||||
format!("{:02}", hour)
|
||||
} else {
|
||||
format!("{}", hour)
|
||||
};
|
||||
out.push_str(&s);
|
||||
}
|
||||
'm' => {
|
||||
@@ -168,7 +176,11 @@ fn format_date_time(tm: &Tm, ms: i32, format: &str) -> String {
|
||||
out.push_str(&s);
|
||||
}
|
||||
'z' => {
|
||||
let s = if run >= 3 { format!("{:03}", ms) } else { format!("{}", ms) };
|
||||
let s = if run >= 3 {
|
||||
format!("{:03}", ms)
|
||||
} else {
|
||||
format!("{}", ms)
|
||||
};
|
||||
out.push_str(&s);
|
||||
}
|
||||
'A' | 'a' => {
|
||||
@@ -250,7 +262,13 @@ impl NodeBehavior for TimeFormatNode {
|
||||
/// anonymous-namespace `format_date_time()`: h/hh is 12-hour only when
|
||||
/// an AM/PM token is present; A/AP/ap/a emit the full AM/PM string),
|
||||
/// and pushes the result as a text value.
|
||||
fn value(&self, core: &NodeCore, inputs: &NodeValueRow, time: Rational, table: &mut NodeValueTable) {
|
||||
fn value(
|
||||
&self,
|
||||
core: &NodeCore,
|
||||
inputs: &NodeValueRow,
|
||||
time: Rational,
|
||||
table: &mut NodeValueTable,
|
||||
) {
|
||||
let time_val = inputs
|
||||
.get(TIME_INPUT)
|
||||
.cloned()
|
||||
@@ -299,7 +317,10 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
);
|
||||
time.properties = vec![
|
||||
("min".to_string(), crate::value::NodeValue::Float(0.0)),
|
||||
("max".to_string(), crate::value::NodeValue::Float(2147483647.0)),
|
||||
(
|
||||
"max".to_string(),
|
||||
crate::value::NodeValue::Float(2147483647.0),
|
||||
),
|
||||
];
|
||||
core.add_input(time);
|
||||
core.add_input(crate::input::Input::new(
|
||||
@@ -351,7 +372,10 @@ mod tests {
|
||||
let n = TimeFormatNode;
|
||||
assert_eq!(n.input_name(TIME_INPUT), "Time");
|
||||
assert_eq!(n.input_name(FORMAT_INPUT), "Format");
|
||||
assert_eq!(n.input_name(LOCAL_TIME_INPUT), "Interpret time as local time");
|
||||
assert_eq!(
|
||||
n.input_name(LOCAL_TIME_INPUT),
|
||||
"Interpret time as local time"
|
||||
);
|
||||
assert_eq!(n.input_name("other_in"), "other_in");
|
||||
}
|
||||
|
||||
@@ -359,13 +383,22 @@ mod tests {
|
||||
fn create_wires_inputs() {
|
||||
let (core, behavior) = create();
|
||||
assert_eq!(behavior.type_id(), "org.olivevideoeditor.Olive.timeformat");
|
||||
assert_eq!(core.get_input(TIME_INPUT).unwrap().value_type, ValueType::Float);
|
||||
assert_eq!(core.get_input(FORMAT_INPUT).unwrap().value_type, ValueType::Text);
|
||||
assert_eq!(
|
||||
core.get_input(TIME_INPUT).unwrap().value_type,
|
||||
ValueType::Float
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(FORMAT_INPUT).unwrap().value_type,
|
||||
ValueType::Text
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(FORMAT_INPUT).unwrap().default,
|
||||
NodeValue::Text("hh:mm:ss".to_string())
|
||||
);
|
||||
assert_eq!(core.get_input(LOCAL_TIME_INPUT).unwrap().value_type, ValueType::Boolean);
|
||||
assert_eq!(
|
||||
core.get_input(LOCAL_TIME_INPUT).unwrap().value_type,
|
||||
ValueType::Boolean
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -410,7 +443,10 @@ mod tests {
|
||||
let t = tm(0, 0, 13, 9, 7, 124);
|
||||
assert_eq!(format_date_time(&t, 0, "'Literal text'"), "Literal text");
|
||||
assert_eq!(format_date_time(&t, 0, "'It''s' HH"), "Its 13");
|
||||
assert_eq!(format_date_time(&t, 0, "yyyy'unterminated"), "2024unterminated");
|
||||
assert_eq!(
|
||||
format_date_time(&t, 0, "yyyy'unterminated"),
|
||||
"2024unterminated"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -424,9 +460,18 @@ mod tests {
|
||||
let (mut core, behavior) = create();
|
||||
// 12:34:56 on 1970-01-01 UTC = 45296 seconds.
|
||||
core.set_standard_value(TIME_INPUT, -1, NodeValue::Float(45296.0));
|
||||
core.set_standard_value(FORMAT_INPUT, -1, NodeValue::Text("yyyy-MM-dd HH:mm:ss".to_string()));
|
||||
core.set_standard_value(
|
||||
FORMAT_INPUT,
|
||||
-1,
|
||||
NodeValue::Text("yyyy-MM-dd HH:mm:ss".to_string()),
|
||||
);
|
||||
let mut table = NodeValueTable::default();
|
||||
behavior.value(&core, &crate::value::NodeValueRow::default(), Rational::new(0, 1), &mut table);
|
||||
behavior.value(
|
||||
&core,
|
||||
&crate::value::NodeValueRow::default(),
|
||||
Rational::new(0, 1),
|
||||
&mut table,
|
||||
);
|
||||
assert_eq!(
|
||||
table.get(ValueType::Text),
|
||||
Some(&NodeValue::Text("1970-01-01 12:34:56".to_string()))
|
||||
@@ -438,9 +483,18 @@ mod tests {
|
||||
let (mut core, behavior) = create();
|
||||
// Same instant plus 789 ms.
|
||||
core.set_standard_value(TIME_INPUT, -1, NodeValue::Float(45296.789));
|
||||
core.set_standard_value(FORMAT_INPUT, -1, NodeValue::Text("HH:mm:ss.zzz".to_string()));
|
||||
core.set_standard_value(
|
||||
FORMAT_INPUT,
|
||||
-1,
|
||||
NodeValue::Text("HH:mm:ss.zzz".to_string()),
|
||||
);
|
||||
let mut table = NodeValueTable::default();
|
||||
behavior.value(&core, &crate::value::NodeValueRow::default(), Rational::new(0, 1), &mut table);
|
||||
behavior.value(
|
||||
&core,
|
||||
&crate::value::NodeValueRow::default(),
|
||||
Rational::new(0, 1),
|
||||
&mut table,
|
||||
);
|
||||
assert_eq!(
|
||||
table.get(ValueType::Text),
|
||||
Some(&NodeValue::Text("12:34:56.789".to_string()))
|
||||
@@ -452,7 +506,10 @@ mod tests {
|
||||
let (core, behavior) = create();
|
||||
let mut row = crate::value::NodeValueRow::default();
|
||||
row.insert(TIME_INPUT.to_string(), NodeValue::Float(45296.0));
|
||||
row.insert(FORMAT_INPUT.to_string(), NodeValue::Text("yyyy".to_string()));
|
||||
row.insert(
|
||||
FORMAT_INPUT.to_string(),
|
||||
NodeValue::Text("yyyy".to_string()),
|
||||
);
|
||||
row.insert(LOCAL_TIME_INPUT.to_string(), NodeValue::Boolean(false));
|
||||
let mut table = NodeValueTable::default();
|
||||
behavior.value(&core, &row, Rational::new(0, 1), &mut table);
|
||||
@@ -473,22 +530,42 @@ mod tests {
|
||||
// each flag value), not the C library itself.
|
||||
let mut secs: c_long = 45296;
|
||||
let mut local: Tm = unsafe { std::mem::zeroed() };
|
||||
unsafe { localtime_r(&secs, &mut local); }
|
||||
unsafe {
|
||||
localtime_r(&secs, &mut local);
|
||||
}
|
||||
let mut utc: Tm = unsafe { std::mem::zeroed() };
|
||||
unsafe { gmtime_r(&secs, &mut utc); }
|
||||
unsafe {
|
||||
gmtime_r(&secs, &mut utc);
|
||||
}
|
||||
let local_expected = format!("{:04}", local.tm_year + 1900);
|
||||
let utc_expected = format!("{:04}", utc.tm_year + 1900);
|
||||
assert_eq!(utc_expected, "1970");
|
||||
|
||||
core.set_standard_value(LOCAL_TIME_INPUT, -1, NodeValue::Boolean(true));
|
||||
let mut table = NodeValueTable::default();
|
||||
behavior.value(&core, &crate::value::NodeValueRow::default(), Rational::new(0, 1), &mut table);
|
||||
assert_eq!(table.get(ValueType::Text), Some(&NodeValue::Text(local_expected)));
|
||||
behavior.value(
|
||||
&core,
|
||||
&crate::value::NodeValueRow::default(),
|
||||
Rational::new(0, 1),
|
||||
&mut table,
|
||||
);
|
||||
assert_eq!(
|
||||
table.get(ValueType::Text),
|
||||
Some(&NodeValue::Text(local_expected))
|
||||
);
|
||||
|
||||
core.set_standard_value(LOCAL_TIME_INPUT, -1, NodeValue::Boolean(false));
|
||||
let mut table = NodeValueTable::default();
|
||||
behavior.value(&core, &crate::value::NodeValueRow::default(), Rational::new(0, 1), &mut table);
|
||||
assert_eq!(table.get(ValueType::Text), Some(&NodeValue::Text(utc_expected)));
|
||||
behavior.value(
|
||||
&core,
|
||||
&crate::value::NodeValueRow::default(),
|
||||
Rational::new(0, 1),
|
||||
&mut table,
|
||||
);
|
||||
assert_eq!(
|
||||
table.get(ValueType::Text),
|
||||
Some(&NodeValue::Text(utc_expected))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -53,7 +53,13 @@ impl NodeBehavior for TimeInput {
|
||||
/// Evaluate outputs (C++ `value()`): pushes the current global time
|
||||
/// (`globals.time().in().to_double()`, here the `time` argument) as a
|
||||
/// float value, not marked as a texture, with the push tag `"time"`.
|
||||
fn value(&self, core: &NodeCore, inputs: &NodeValueRow, time: Rational, table: &mut NodeValueTable) {
|
||||
fn value(
|
||||
&self,
|
||||
core: &NodeCore,
|
||||
inputs: &NodeValueRow,
|
||||
time: Rational,
|
||||
table: &mut NodeValueTable,
|
||||
) {
|
||||
let _ = (core, inputs);
|
||||
table.push(
|
||||
crate::value::ValueType::Float,
|
||||
@@ -97,7 +103,12 @@ mod tests {
|
||||
fn value_pushes_current_time_as_float() {
|
||||
let (core, behavior) = create();
|
||||
let mut table = NodeValueTable::default();
|
||||
behavior.value(&core, &crate::value::NodeValueRow::default(), Rational::new(15, 2), &mut table);
|
||||
behavior.value(
|
||||
&core,
|
||||
&crate::value::NodeValueRow::default(),
|
||||
Rational::new(15, 2),
|
||||
&mut table,
|
||||
);
|
||||
assert_eq!(table.get(ValueType::Float), Some(&NodeValue::Float(7.5)));
|
||||
// The pushed row carries the "time" tag (C++ push tag).
|
||||
let (_, _, tag) = &table.rows()[0];
|
||||
|
||||
@@ -153,7 +153,13 @@ impl NodeBehavior for TimeOffsetNode {
|
||||
/// [`Self::input_time_adjustment_with`] (and tested there); until the
|
||||
/// adjustment API gains core access, the identity range is returned
|
||||
/// (`// CPP-PARITY: timeoffsetnode.cpp` `input_time_adjustment`).
|
||||
fn input_time_adjustment(&self, input: &str, element: i32, time: TimeRange, traverse: bool) -> TimeRange {
|
||||
fn input_time_adjustment(
|
||||
&self,
|
||||
input: &str,
|
||||
element: i32,
|
||||
time: TimeRange,
|
||||
traverse: bool,
|
||||
) -> TimeRange {
|
||||
let _ = (input, element, traverse);
|
||||
time
|
||||
}
|
||||
@@ -167,7 +173,13 @@ impl NodeBehavior for TimeOffsetNode {
|
||||
/// As with the input side, the value read needs the node's data; the
|
||||
/// exact remap is ported in [`Self::output_time_adjustment_with`]
|
||||
/// (`// CPP-PARITY: timeoffsetnode.cpp` `output_time_adjustment`).
|
||||
fn output_time_adjustment(&self, input: &str, element: i32, time: TimeRange, traverse: bool) -> TimeRange {
|
||||
fn output_time_adjustment(
|
||||
&self,
|
||||
input: &str,
|
||||
element: i32,
|
||||
time: TimeRange,
|
||||
traverse: bool,
|
||||
) -> TimeRange {
|
||||
let _ = (input, element, traverse);
|
||||
time
|
||||
}
|
||||
@@ -175,7 +187,13 @@ impl NodeBehavior for TimeOffsetNode {
|
||||
/// Evaluate outputs (C++ `value()`): pushes the value arriving at
|
||||
/// `input_in` through unchanged (the actual time shift happens via the
|
||||
/// time-adjustment overrides above).
|
||||
fn value(&self, core: &NodeCore, inputs: &NodeValueRow, time: Rational, table: &mut NodeValueTable) {
|
||||
fn value(
|
||||
&self,
|
||||
core: &NodeCore,
|
||||
inputs: &NodeValueRow,
|
||||
time: Rational,
|
||||
table: &mut NodeValueTable,
|
||||
) {
|
||||
let _ = (core, time);
|
||||
// `table->push(value.at(k_input_input))` — the value passes through
|
||||
// unchanged, whatever its type (texture values included).
|
||||
@@ -208,11 +226,8 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
];
|
||||
core.add_input(time_input);
|
||||
|
||||
let mut input_input = crate::input::Input::new(
|
||||
INPUT_INPUT,
|
||||
crate::value::ValueType::None,
|
||||
NodeValue::None,
|
||||
);
|
||||
let mut input_input =
|
||||
crate::input::Input::new(INPUT_INPUT, crate::value::ValueType::None, NodeValue::None);
|
||||
input_input.flags |= crate::input::flags::NOT_KEYFRAMABLE;
|
||||
core.add_input(input_input);
|
||||
|
||||
@@ -254,12 +269,14 @@ mod tests {
|
||||
assert_eq!(time_in.value_type, ValueType::Rational);
|
||||
assert_eq!(time_in.default, NodeValue::Rational(Rational::new(0, 1)));
|
||||
assert_ne!(time_in.flags & crate::input::flags::NOT_CONNECTABLE, 0);
|
||||
assert!(time_in.properties.iter().any(|(k, v)| {
|
||||
k == "view" && v == &NodeValue::Text("time".to_string())
|
||||
}));
|
||||
assert!(time_in.properties.iter().any(|(k, v)| {
|
||||
k == "viewlock" && v == &NodeValue::Boolean(true)
|
||||
}));
|
||||
assert!(time_in
|
||||
.properties
|
||||
.iter()
|
||||
.any(|(k, v)| { k == "view" && v == &NodeValue::Text("time".to_string()) }));
|
||||
assert!(time_in
|
||||
.properties
|
||||
.iter()
|
||||
.any(|(k, v)| { k == "viewlock" && v == &NodeValue::Boolean(true) }));
|
||||
let input_in = core.get_input(INPUT_INPUT).unwrap();
|
||||
assert_eq!(input_in.value_type, ValueType::None);
|
||||
assert_ne!(input_in.flags & crate::input::flags::NOT_KEYFRAMABLE, 0);
|
||||
@@ -320,20 +337,22 @@ mod tests {
|
||||
let (mut core, _) = create();
|
||||
// A non-constant (keyframed) offset: each endpoint is shifted by the
|
||||
// time_in value evaluated at that endpoint.
|
||||
core.keyframe_track_mut(TIME_INPUT, -1).set_key(crate::keyframe::Keyframe {
|
||||
time: Rational::new(0, 1),
|
||||
value: NodeValue::Rational(Rational::new(1, 1)),
|
||||
interpolation: crate::keyframe::Interpolation::Linear,
|
||||
bezier_in: (0.0, 0.0),
|
||||
bezier_out: (0.0, 0.0),
|
||||
});
|
||||
core.keyframe_track_mut(TIME_INPUT, -1).set_key(crate::keyframe::Keyframe {
|
||||
time: Rational::new(20, 1),
|
||||
value: NodeValue::Rational(Rational::new(3, 1)),
|
||||
interpolation: crate::keyframe::Interpolation::Linear,
|
||||
bezier_in: (0.0, 0.0),
|
||||
bezier_out: (0.0, 0.0),
|
||||
});
|
||||
core.keyframe_track_mut(TIME_INPUT, -1)
|
||||
.set_key(crate::keyframe::Keyframe {
|
||||
time: Rational::new(0, 1),
|
||||
value: NodeValue::Rational(Rational::new(1, 1)),
|
||||
interpolation: crate::keyframe::Interpolation::Linear,
|
||||
bezier_in: (0.0, 0.0),
|
||||
bezier_out: (0.0, 0.0),
|
||||
});
|
||||
core.keyframe_track_mut(TIME_INPUT, -1)
|
||||
.set_key(crate::keyframe::Keyframe {
|
||||
time: Rational::new(20, 1),
|
||||
value: NodeValue::Rational(Rational::new(3, 1)),
|
||||
interpolation: crate::keyframe::Interpolation::Linear,
|
||||
bezier_in: (0.0, 0.0),
|
||||
bezier_out: (0.0, 0.0),
|
||||
});
|
||||
let t = TimeRange::new(Rational::new(0, 1), Rational::new(20, 1));
|
||||
let shifted = TimeOffsetNode::input_time_adjustment_with(&core, INPUT_INPUT, -1, t, true);
|
||||
// 0 + offset(0s) = 1; 20 + offset(20s) = 23.
|
||||
@@ -349,7 +368,8 @@ mod tests {
|
||||
let shifted = TimeOffsetNode::input_time_adjustment_with(&core, INPUT_INPUT, -1, t, true);
|
||||
assert_eq!(shifted.in_(), Rational::new(15, 1));
|
||||
assert_eq!(shifted.out(), Rational::new(25, 1));
|
||||
let unshifted = TimeOffsetNode::output_time_adjustment_with(&core, INPUT_INPUT, -1, shifted, true);
|
||||
let unshifted =
|
||||
TimeOffsetNode::output_time_adjustment_with(&core, INPUT_INPUT, -1, shifted, true);
|
||||
assert_eq!(unshifted, t);
|
||||
}
|
||||
|
||||
@@ -358,8 +378,14 @@ mod tests {
|
||||
let (mut core, _) = create();
|
||||
core.set_standard_value(TIME_INPUT, -1, NodeValue::Rational(Rational::new(5, 1)));
|
||||
let t = TimeRange::new(Rational::new(10, 1), Rational::new(20, 1));
|
||||
assert_eq!(TimeOffsetNode::input_time_adjustment_with(&core, "other_in", -1, t, true), t);
|
||||
assert_eq!(TimeOffsetNode::output_time_adjustment_with(&core, "other_in", -1, t, true), t);
|
||||
assert_eq!(
|
||||
TimeOffsetNode::input_time_adjustment_with(&core, "other_in", -1, t, true),
|
||||
t
|
||||
);
|
||||
assert_eq!(
|
||||
TimeOffsetNode::output_time_adjustment_with(&core, "other_in", -1, t, true),
|
||||
t
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -123,7 +123,13 @@ impl NodeBehavior for TimeRemapNode {
|
||||
/// [`Self::input_time_adjustment_with`] (and tested there); until the
|
||||
/// adjustment API gains core access, the identity range is returned
|
||||
/// (`// CPP-PARITY: timeremap.cpp` `input_time_adjustment`).
|
||||
fn input_time_adjustment(&self, input: &str, element: i32, time: TimeRange, traverse: bool) -> TimeRange {
|
||||
fn input_time_adjustment(
|
||||
&self,
|
||||
input: &str,
|
||||
element: i32,
|
||||
time: TimeRange,
|
||||
traverse: bool,
|
||||
) -> TimeRange {
|
||||
let _ = (input, element, traverse);
|
||||
time
|
||||
}
|
||||
@@ -132,7 +138,13 @@ impl NodeBehavior for TimeRemapNode {
|
||||
/// override has its real inverse implementation commented out (an
|
||||
/// arbitrary remap is not invertible) and unconditionally defers to the
|
||||
/// base-class identity behavior; declared here for parity.
|
||||
fn output_time_adjustment(&self, input: &str, element: i32, time: TimeRange, traverse: bool) -> TimeRange {
|
||||
fn output_time_adjustment(
|
||||
&self,
|
||||
input: &str,
|
||||
element: i32,
|
||||
time: TimeRange,
|
||||
traverse: bool,
|
||||
) -> TimeRange {
|
||||
let _ = (input, element, traverse);
|
||||
time
|
||||
}
|
||||
@@ -140,7 +152,13 @@ impl NodeBehavior for TimeRemapNode {
|
||||
/// Evaluate outputs (C++ `value()`): pushes the value arriving at
|
||||
/// `input_in` through unchanged (the actual time remap happens via the
|
||||
/// time-adjustment overrides above).
|
||||
fn value(&self, core: &NodeCore, inputs: &NodeValueRow, time: Rational, table: &mut NodeValueTable) {
|
||||
fn value(
|
||||
&self,
|
||||
core: &NodeCore,
|
||||
inputs: &NodeValueRow,
|
||||
time: Rational,
|
||||
table: &mut NodeValueTable,
|
||||
) {
|
||||
let _ = (core, time);
|
||||
// `table->push(value.at(k_input_input))` — the value passes through
|
||||
// unchanged, whatever its type (texture values included).
|
||||
@@ -173,11 +191,8 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
];
|
||||
core.add_input(time_input);
|
||||
|
||||
let mut input_input = crate::input::Input::new(
|
||||
INPUT_INPUT,
|
||||
crate::value::ValueType::None,
|
||||
NodeValue::None,
|
||||
);
|
||||
let mut input_input =
|
||||
crate::input::Input::new(INPUT_INPUT, crate::value::ValueType::None, NodeValue::None);
|
||||
input_input.flags |= crate::input::flags::NOT_KEYFRAMABLE;
|
||||
core.add_input(input_input);
|
||||
|
||||
@@ -219,12 +234,14 @@ mod tests {
|
||||
assert_eq!(time_in.value_type, ValueType::Rational);
|
||||
assert_eq!(time_in.default, NodeValue::Rational(Rational::new(0, 1)));
|
||||
assert_ne!(time_in.flags & crate::input::flags::NOT_CONNECTABLE, 0);
|
||||
assert!(time_in.properties.iter().any(|(k, v)| {
|
||||
k == "view" && v == &NodeValue::Text("time".to_string())
|
||||
}));
|
||||
assert!(time_in.properties.iter().any(|(k, v)| {
|
||||
k == "viewlock" && v == &NodeValue::Boolean(true)
|
||||
}));
|
||||
assert!(time_in
|
||||
.properties
|
||||
.iter()
|
||||
.any(|(k, v)| { k == "view" && v == &NodeValue::Text("time".to_string()) }));
|
||||
assert!(time_in
|
||||
.properties
|
||||
.iter()
|
||||
.any(|(k, v)| { k == "viewlock" && v == &NodeValue::Boolean(true) }));
|
||||
let input_in = core.get_input(INPUT_INPUT).unwrap();
|
||||
assert_eq!(input_in.value_type, ValueType::None);
|
||||
assert_ne!(input_in.flags & crate::input::flags::NOT_KEYFRAMABLE, 0);
|
||||
@@ -234,8 +251,14 @@ mod tests {
|
||||
fn get_remapped_time_uses_standard_value() {
|
||||
let (mut core, _) = create();
|
||||
core.set_standard_value(TIME_INPUT, -1, NodeValue::Rational(Rational::new(5, 1)));
|
||||
assert_eq!(TimeRemapNode::get_remapped_time(&core, Rational::new(0, 1)), Rational::new(5, 1));
|
||||
assert_eq!(TimeRemapNode::get_remapped_time(&core, Rational::new(30, 1)), Rational::new(5, 1));
|
||||
assert_eq!(
|
||||
TimeRemapNode::get_remapped_time(&core, Rational::new(0, 1)),
|
||||
Rational::new(5, 1)
|
||||
);
|
||||
assert_eq!(
|
||||
TimeRemapNode::get_remapped_time(&core, Rational::new(30, 1)),
|
||||
Rational::new(5, 1)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -244,24 +267,35 @@ mod tests {
|
||||
// A time-remap curve: at 0s the input shows 10s, at 10s it shows 0s
|
||||
// (reverse). Evaluated exactly at the keyframe times, so no
|
||||
// interpolation is involved.
|
||||
core.keyframe_track_mut(TIME_INPUT, -1).set_key(crate::keyframe::Keyframe {
|
||||
time: Rational::new(0, 1),
|
||||
value: NodeValue::Rational(Rational::new(10, 1)),
|
||||
interpolation: crate::keyframe::Interpolation::Linear,
|
||||
bezier_in: (0.0, 0.0),
|
||||
bezier_out: (0.0, 0.0),
|
||||
});
|
||||
core.keyframe_track_mut(TIME_INPUT, -1).set_key(crate::keyframe::Keyframe {
|
||||
time: Rational::new(10, 1),
|
||||
value: NodeValue::Rational(Rational::new(0, 1)),
|
||||
interpolation: crate::keyframe::Interpolation::Linear,
|
||||
bezier_in: (0.0, 0.0),
|
||||
bezier_out: (0.0, 0.0),
|
||||
});
|
||||
assert_eq!(TimeRemapNode::get_remapped_time(&core, Rational::new(0, 1)), Rational::new(10, 1));
|
||||
assert_eq!(TimeRemapNode::get_remapped_time(&core, Rational::new(10, 1)), Rational::new(0, 1));
|
||||
core.keyframe_track_mut(TIME_INPUT, -1)
|
||||
.set_key(crate::keyframe::Keyframe {
|
||||
time: Rational::new(0, 1),
|
||||
value: NodeValue::Rational(Rational::new(10, 1)),
|
||||
interpolation: crate::keyframe::Interpolation::Linear,
|
||||
bezier_in: (0.0, 0.0),
|
||||
bezier_out: (0.0, 0.0),
|
||||
});
|
||||
core.keyframe_track_mut(TIME_INPUT, -1)
|
||||
.set_key(crate::keyframe::Keyframe {
|
||||
time: Rational::new(10, 1),
|
||||
value: NodeValue::Rational(Rational::new(0, 1)),
|
||||
interpolation: crate::keyframe::Interpolation::Linear,
|
||||
bezier_in: (0.0, 0.0),
|
||||
bezier_out: (0.0, 0.0),
|
||||
});
|
||||
assert_eq!(
|
||||
TimeRemapNode::get_remapped_time(&core, Rational::new(0, 1)),
|
||||
Rational::new(10, 1)
|
||||
);
|
||||
assert_eq!(
|
||||
TimeRemapNode::get_remapped_time(&core, Rational::new(10, 1)),
|
||||
Rational::new(0, 1)
|
||||
);
|
||||
// Mid-way between the keys the curve is linear: 10s -> 5s.
|
||||
assert_eq!(TimeRemapNode::get_remapped_time(&core, Rational::new(5, 1)), Rational::new(5, 1));
|
||||
assert_eq!(
|
||||
TimeRemapNode::get_remapped_time(&core, Rational::new(5, 1)),
|
||||
Rational::new(5, 1)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -294,14 +328,8 @@ mod tests {
|
||||
fn output_time_adjustment_is_identity() {
|
||||
let n = TimeRemapNode;
|
||||
let t = TimeRange::new(Rational::new(10, 1), Rational::new(20, 1));
|
||||
assert_eq!(
|
||||
n.output_time_adjustment(INPUT_INPUT, -1, t, true),
|
||||
t
|
||||
);
|
||||
assert_eq!(
|
||||
n.output_time_adjustment("other_in", -1, t, true),
|
||||
t
|
||||
);
|
||||
assert_eq!(n.output_time_adjustment(INPUT_INPUT, -1, t, true), t);
|
||||
assert_eq!(n.output_time_adjustment("other_in", -1, t, true), t);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -318,7 +346,12 @@ mod tests {
|
||||
fn value_pushes_nothing_when_input_absent() {
|
||||
let (core, behavior) = create();
|
||||
let mut table = NodeValueTable::default();
|
||||
behavior.value(&core, &crate::value::NodeValueRow::default(), Rational::new(0, 1), &mut table);
|
||||
behavior.value(
|
||||
&core,
|
||||
&crate::value::NodeValueRow::default(),
|
||||
Rational::new(0, 1),
|
||||
&mut table,
|
||||
);
|
||||
assert!(table.is_empty());
|
||||
}
|
||||
|
||||
|
||||
@@ -177,11 +177,7 @@ impl TransformDistortNode {
|
||||
);
|
||||
|
||||
// Apply offset if applicable.
|
||||
adjusted_matrix = super::matrix::matrix_translate(
|
||||
adjusted_matrix,
|
||||
offset.0,
|
||||
offset.1,
|
||||
);
|
||||
adjusted_matrix = super::matrix::matrix_translate(adjusted_matrix, offset.0, offset.1);
|
||||
|
||||
// Adjust by the matrix we generated earlier.
|
||||
adjusted_matrix = super::matrix::matrix_mul(adjusted_matrix, mat);
|
||||
@@ -345,13 +341,7 @@ impl NodeBehavior for TransformDistortNode {
|
||||
|
||||
// Generate matrix.
|
||||
let generated_matrix = super::matrix::MatrixGenerator::generate_matrix(
|
||||
inputs,
|
||||
core,
|
||||
time,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
parent,
|
||||
inputs, core, time, false, false, false, parent,
|
||||
);
|
||||
table.push(
|
||||
crate::value::ValueType::Matrix,
|
||||
@@ -514,11 +504,7 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
// `pos_in`), and the eight scale point gizmos (absolute drag
|
||||
// behavior, bound to `scale_in`) in `k_gizmo_scale_*` order.
|
||||
let rotation_gizmo = Gizmo {
|
||||
position_inputs: vec![(
|
||||
super::matrix::ROTATION_INPUT.to_string(),
|
||||
-1,
|
||||
0,
|
||||
)],
|
||||
position_inputs: vec![(super::matrix::ROTATION_INPUT.to_string(), -1, 0)],
|
||||
drag_point: (0.0, 0.0),
|
||||
};
|
||||
let poly_gizmo = Gizmo {
|
||||
@@ -640,14 +626,23 @@ mod tests {
|
||||
assert_eq!(n.input_name(TEXTURE_INPUT), "Texture");
|
||||
assert_eq!(n.input_name(INTERPOLATION_INPUT), "Interpolation");
|
||||
// Inherited matrix-generator names.
|
||||
assert_eq!(n.input_name(super::super::matrix::POSITION_INPUT), "Position");
|
||||
assert_eq!(n.input_name(super::super::matrix::ROTATION_INPUT), "Rotation");
|
||||
assert_eq!(
|
||||
n.input_name(super::super::matrix::POSITION_INPUT),
|
||||
"Position"
|
||||
);
|
||||
assert_eq!(
|
||||
n.input_name(super::super::matrix::ROTATION_INPUT),
|
||||
"Rotation"
|
||||
);
|
||||
assert_eq!(n.input_name(super::super::matrix::SCALE_INPUT), "Scale");
|
||||
assert_eq!(
|
||||
n.input_name(super::super::matrix::UNIFORM_SCALE_INPUT),
|
||||
"Uniform Scale"
|
||||
);
|
||||
assert_eq!(n.input_name(super::super::matrix::ANCHOR_INPUT), "Anchor Point");
|
||||
assert_eq!(
|
||||
n.input_name(super::super::matrix::ANCHOR_INPUT),
|
||||
"Anchor Point"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -802,7 +797,10 @@ mod tests {
|
||||
);
|
||||
assert!((m[0] - 1.0).abs() < 1e-12);
|
||||
assert!((m[5] - 1.0).abs() < 1e-12);
|
||||
assert!((m[3] - 100.0 * 2.0 / 640.0).abs() < 1e-12, "offset scaled into sequence units");
|
||||
assert!(
|
||||
(m[3] - 100.0 * 2.0 / 640.0).abs() < 1e-12,
|
||||
"offset scaled into sequence units"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -120,7 +120,11 @@ impl NodeBehavior for TrigonometryNode {
|
||||
Operation::HyperbolicTangent => x = x.tanh(),
|
||||
}
|
||||
|
||||
table.push(crate::value::ValueType::Float, crate::value::NodeValue::Float(x), None);
|
||||
table.push(
|
||||
crate::value::ValueType::Float,
|
||||
crate::value::NodeValue::Float(x),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deep copy (C++ `copy()`).
|
||||
@@ -161,11 +165,7 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
method.flags |= crate::input::flags::NOT_CONNECTABLE | crate::input::flags::NOT_KEYFRAMABLE;
|
||||
method.properties = vec![(
|
||||
"combobox_strings".to_string(),
|
||||
crate::value::NodeValue::Binary(
|
||||
OPERATION_NAMES
|
||||
.concat()
|
||||
.into_bytes(),
|
||||
),
|
||||
crate::value::NodeValue::Binary(OPERATION_NAMES.concat().into_bytes()),
|
||||
)];
|
||||
core.add_input(method);
|
||||
|
||||
@@ -209,8 +209,14 @@ mod tests {
|
||||
#[test]
|
||||
fn create_wires_inputs() {
|
||||
let (core, behavior) = create();
|
||||
assert_eq!(behavior.type_id(), "org.olivevideoeditor.Olive.trigonometry");
|
||||
assert_eq!(core.get_input(X_INPUT).unwrap().default, NodeValue::Float(0.0));
|
||||
assert_eq!(
|
||||
behavior.type_id(),
|
||||
"org.olivevideoeditor.Olive.trigonometry"
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(X_INPUT).unwrap().default,
|
||||
NodeValue::Float(0.0)
|
||||
);
|
||||
let method = core.get_input(METHOD_INPUT).unwrap();
|
||||
assert_ne!(method.flags & crate::input::flags::NOT_CONNECTABLE, 0);
|
||||
}
|
||||
@@ -231,10 +237,8 @@ mod tests {
|
||||
fn value_cosine_and_tangent() {
|
||||
let (mut core, behavior) = create();
|
||||
core.set_standard_value(METHOD_INPUT, -1, NodeValue::Combo(1));
|
||||
let inputs = crate::value::NodeValueRow::from([(
|
||||
X_INPUT.to_string(),
|
||||
NodeValue::Float(0.0),
|
||||
)]);
|
||||
let inputs =
|
||||
crate::value::NodeValueRow::from([(X_INPUT.to_string(), NodeValue::Float(0.0))]);
|
||||
let mut table = NodeValueTable::default();
|
||||
behavior.value(&core, &inputs, Rational::new(0, 1), &mut table);
|
||||
assert_eq!(table.get(ValueType::Float), Some(&NodeValue::Float(1.0)));
|
||||
@@ -255,7 +259,12 @@ mod tests {
|
||||
let (mut core, behavior) = create();
|
||||
core.set_standard_value(X_INPUT, -1, NodeValue::Float(0.0));
|
||||
let mut table = NodeValueTable::default();
|
||||
behavior.value(&core, &crate::value::NodeValueRow::default(), Rational::new(0, 1), &mut table);
|
||||
behavior.value(
|
||||
&core,
|
||||
&crate::value::NodeValueRow::default(),
|
||||
Rational::new(0, 1),
|
||||
&mut table,
|
||||
);
|
||||
assert_eq!(table.get(ValueType::Float), Some(&NodeValue::Float(0.0)));
|
||||
}
|
||||
|
||||
@@ -289,29 +298,39 @@ mod tests {
|
||||
];
|
||||
for (op, x, f) in cases {
|
||||
core.set_standard_value(METHOD_INPUT, -1, NodeValue::Combo(op));
|
||||
let inputs = crate::value::NodeValueRow::from([(
|
||||
X_INPUT.to_string(),
|
||||
NodeValue::Float(x),
|
||||
)]);
|
||||
let inputs =
|
||||
crate::value::NodeValueRow::from([(X_INPUT.to_string(), NodeValue::Float(x))]);
|
||||
let mut table = NodeValueTable::default();
|
||||
behavior.value(&core, &inputs, Rational::new(0, 1), &mut table);
|
||||
let got = table.get(ValueType::Float).unwrap().to_double();
|
||||
assert!((got - f(x)).abs() < 1e-12, "op {}: got {}, want {}", op, got, f(x));
|
||||
assert!(
|
||||
(got - f(x)).abs() < 1e-12,
|
||||
"op {}: got {}, want {}",
|
||||
op,
|
||||
got,
|
||||
f(x)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn value_keyframed_operand() {
|
||||
let (mut core, behavior) = create();
|
||||
core.keyframe_track_mut(X_INPUT, -1).set_key(crate::keyframe::Keyframe {
|
||||
time: Rational::new(0, 1),
|
||||
value: NodeValue::Float(1.0),
|
||||
interpolation: crate::keyframe::Interpolation::Linear,
|
||||
bezier_in: (0.0, 0.0),
|
||||
bezier_out: (0.0, 0.0),
|
||||
});
|
||||
core.keyframe_track_mut(X_INPUT, -1)
|
||||
.set_key(crate::keyframe::Keyframe {
|
||||
time: Rational::new(0, 1),
|
||||
value: NodeValue::Float(1.0),
|
||||
interpolation: crate::keyframe::Interpolation::Linear,
|
||||
bezier_in: (0.0, 0.0),
|
||||
bezier_out: (0.0, 0.0),
|
||||
});
|
||||
let mut table = NodeValueTable::default();
|
||||
behavior.value(&core, &crate::value::NodeValueRow::default(), Rational::new(0, 1), &mut table);
|
||||
behavior.value(
|
||||
&core,
|
||||
&crate::value::NodeValueRow::default(),
|
||||
Rational::new(0, 1),
|
||||
&mut table,
|
||||
);
|
||||
let v = table.get(ValueType::Float).unwrap().to_double();
|
||||
assert!((v - 1.0_f64.sin()).abs() < 1e-12);
|
||||
}
|
||||
|
||||
@@ -166,7 +166,10 @@ mod tests {
|
||||
fn create_wires_inputs() {
|
||||
let (core, behavior) = create();
|
||||
assert_eq!(behavior.type_id(), "org.olivevideoeditor.Olive.value");
|
||||
assert_eq!(core.get_input(VALUE_INPUT).unwrap().value_type, ValueType::Float);
|
||||
assert_eq!(
|
||||
core.get_input(VALUE_INPUT).unwrap().value_type,
|
||||
ValueType::Float
|
||||
);
|
||||
assert_eq!(
|
||||
core.get_input(TYPE_INPUT).unwrap().flags & crate::input::flags::NOT_CONNECTABLE,
|
||||
crate::input::flags::NOT_CONNECTABLE
|
||||
@@ -178,22 +181,33 @@ mod tests {
|
||||
let (mut core, behavior) = create();
|
||||
core.set_standard_value(VALUE_INPUT, -1, NodeValue::Float(3.5));
|
||||
let mut table = NodeValueTable::default();
|
||||
behavior.value(&core, &crate::value::NodeValueRow::default(), Rational::new(0, 1), &mut table);
|
||||
behavior.value(
|
||||
&core,
|
||||
&crate::value::NodeValueRow::default(),
|
||||
Rational::new(0, 1),
|
||||
&mut table,
|
||||
);
|
||||
assert_eq!(table.get(ValueType::Float), Some(&NodeValue::Float(3.5)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn value_pushes_keyframed_value() {
|
||||
let (mut core, behavior) = create();
|
||||
core.keyframe_track_mut(VALUE_INPUT, -1).set_key(crate::keyframe::Keyframe {
|
||||
time: Rational::new(10, 1),
|
||||
value: NodeValue::Float(9.0),
|
||||
interpolation: crate::keyframe::Interpolation::Linear,
|
||||
bezier_in: (0.0, 0.0),
|
||||
bezier_out: (0.0, 0.0),
|
||||
});
|
||||
core.keyframe_track_mut(VALUE_INPUT, -1)
|
||||
.set_key(crate::keyframe::Keyframe {
|
||||
time: Rational::new(10, 1),
|
||||
value: NodeValue::Float(9.0),
|
||||
interpolation: crate::keyframe::Interpolation::Linear,
|
||||
bezier_in: (0.0, 0.0),
|
||||
bezier_out: (0.0, 0.0),
|
||||
});
|
||||
let mut table = NodeValueTable::default();
|
||||
behavior.value(&core, &crate::value::NodeValueRow::default(), Rational::new(10, 1), &mut table);
|
||||
behavior.value(
|
||||
&core,
|
||||
&crate::value::NodeValueRow::default(),
|
||||
Rational::new(10, 1),
|
||||
&mut table,
|
||||
);
|
||||
assert_eq!(table.get(ValueType::Float), Some(&NodeValue::Float(9.0)));
|
||||
}
|
||||
|
||||
@@ -204,12 +218,18 @@ mod tests {
|
||||
// Change type_in to vec2 (index 3) and fire the event.
|
||||
core.set_standard_value(TYPE_INPUT, -1, NodeValue::Combo(3));
|
||||
behavior.input_value_changed(&mut core, TYPE_INPUT, -1);
|
||||
assert_eq!(core.get_input(VALUE_INPUT).unwrap().value_type, ValueType::Vec2);
|
||||
assert_eq!(
|
||||
core.get_input(VALUE_INPUT).unwrap().value_type,
|
||||
ValueType::Vec2
|
||||
);
|
||||
|
||||
// Out-of-range index leaves the type unchanged.
|
||||
core.set_standard_value(TYPE_INPUT, -1, NodeValue::Combo(99));
|
||||
behavior.input_value_changed(&mut core, TYPE_INPUT, -1);
|
||||
assert_eq!(core.get_input(VALUE_INPUT).unwrap().value_type, ValueType::Vec2);
|
||||
assert_eq!(
|
||||
core.get_input(VALUE_INPUT).unwrap().value_type,
|
||||
ValueType::Vec2
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -134,7 +134,9 @@ impl NodeBehavior for VolumeNode {
|
||||
};
|
||||
let volume = match inputs.get(VOLUME_INPUT) {
|
||||
Some(v) => v.to_double(),
|
||||
None => core.value_at_time(VOLUME_INPUT, -1, range.in_()).to_double(),
|
||||
None => core
|
||||
.value_at_time(VOLUME_INPUT, -1, range.in_())
|
||||
.to_double(),
|
||||
};
|
||||
for c in 0..output.channels {
|
||||
for i in 0..output.sample_count {
|
||||
@@ -157,19 +159,12 @@ impl NodeBehavior for VolumeNode {
|
||||
pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
let mut core = NodeCore::new();
|
||||
|
||||
let mut samples = crate::input::Input::new(
|
||||
SAMPLES_INPUT,
|
||||
ValueType::Samples,
|
||||
NodeValue::None,
|
||||
);
|
||||
let mut samples = crate::input::Input::new(SAMPLES_INPUT, ValueType::Samples, NodeValue::None);
|
||||
samples.flags |= crate::input::flags::NOT_KEYFRAMABLE;
|
||||
core.add_input(samples);
|
||||
|
||||
let mut volume = crate::input::Input::new(
|
||||
VOLUME_INPUT,
|
||||
ValueType::Float,
|
||||
NodeValue::Float(1.0),
|
||||
);
|
||||
let mut volume =
|
||||
crate::input::Input::new(VOLUME_INPUT, ValueType::Float, NodeValue::Float(1.0));
|
||||
volume.properties = vec![
|
||||
("min".to_string(), NodeValue::Float(0.0)),
|
||||
("view".to_string(), NodeValue::Text("decibel".into())),
|
||||
@@ -221,7 +216,10 @@ mod tests {
|
||||
core.get_input(SAMPLES_INPUT).unwrap().flags & crate::input::flags::NOT_KEYFRAMABLE,
|
||||
crate::input::flags::NOT_KEYFRAMABLE
|
||||
);
|
||||
assert_eq!(core.get_input(VOLUME_INPUT).unwrap().default, NodeValue::Float(1.0));
|
||||
assert_eq!(
|
||||
core.get_input(VOLUME_INPUT).unwrap().default,
|
||||
NodeValue::Float(1.0)
|
||||
);
|
||||
assert_eq!(core.effect_input, SAMPLES_INPUT);
|
||||
assert_ne!(core.flags & crate::node::flags::AUDIO_EFFECT, 0);
|
||||
}
|
||||
@@ -279,13 +277,14 @@ mod tests {
|
||||
fn value_dynamic_volume_pushes_through() {
|
||||
let (mut core, behavior) = create();
|
||||
// Keyframing the volume input makes it non-static.
|
||||
core.keyframe_track_mut(VOLUME_INPUT, -1).set_key(crate::keyframe::Keyframe {
|
||||
time: Rational::new(0, 1),
|
||||
value: NodeValue::Float(0.5),
|
||||
interpolation: crate::keyframe::Interpolation::Linear,
|
||||
bezier_in: (0.0, 0.0),
|
||||
bezier_out: (0.0, 0.0),
|
||||
});
|
||||
core.keyframe_track_mut(VOLUME_INPUT, -1)
|
||||
.set_key(crate::keyframe::Keyframe {
|
||||
time: Rational::new(0, 1),
|
||||
value: NodeValue::Float(0.5),
|
||||
interpolation: crate::keyframe::Interpolation::Linear,
|
||||
bezier_in: (0.0, 0.0),
|
||||
bezier_out: (0.0, 0.0),
|
||||
});
|
||||
let buf = planar(1, 2, &[1.0, 2.0]);
|
||||
let inputs = std::collections::BTreeMap::from([(
|
||||
SAMPLES_INPUT.to_string(),
|
||||
|
||||
@@ -227,7 +227,10 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
temperature.properties = vec![
|
||||
("min".to_string(), crate::value::NodeValue::Float(1000.0)),
|
||||
("max".to_string(), crate::value::NodeValue::Float(40000.0)),
|
||||
("view".to_string(), crate::value::NodeValue::Text("normal".into())),
|
||||
(
|
||||
"view".to_string(),
|
||||
crate::value::NodeValue::Text("normal".into()),
|
||||
),
|
||||
];
|
||||
core.add_input(temperature);
|
||||
|
||||
@@ -278,7 +281,10 @@ mod tests {
|
||||
#[test]
|
||||
fn create_wires_inputs_flags_and_properties() {
|
||||
let (core, behavior) = create();
|
||||
assert_eq!(behavior.type_id(), "org.olivevideoeditor.Olive.whitebalance");
|
||||
assert_eq!(
|
||||
behavior.type_id(),
|
||||
"org.olivevideoeditor.Olive.whitebalance"
|
||||
);
|
||||
let tex = core.get_input(TEXTURE_INPUT).unwrap();
|
||||
assert_ne!(tex.flags & crate::input::flags::NOT_KEYFRAMABLE, 0);
|
||||
assert_eq!(
|
||||
@@ -342,7 +348,10 @@ mod tests {
|
||||
assert_eq!(WhiteBalanceNode::gain_for_temperature(6500.0, 1.0)[1], 2.0);
|
||||
assert_eq!(WhiteBalanceNode::gain_for_temperature(6500.0, -1.0)[1], 0.0);
|
||||
assert_eq!(WhiteBalanceNode::gain_for_temperature(6500.0, 10.0)[1], 2.0);
|
||||
assert_eq!(WhiteBalanceNode::gain_for_temperature(6500.0, -10.0)[1], 0.0);
|
||||
assert_eq!(
|
||||
WhiteBalanceNode::gain_for_temperature(6500.0, -10.0)[1],
|
||||
0.0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -355,7 +364,12 @@ mod tests {
|
||||
fn value_no_texture_pushes_nothing() {
|
||||
let (core, behavior) = create();
|
||||
let mut table = NodeValueTable::default();
|
||||
behavior.value(&core, &crate::value::NodeValueRow::default(), Rational::new(0, 1), &mut table);
|
||||
behavior.value(
|
||||
&core,
|
||||
&crate::value::NodeValueRow::default(),
|
||||
Rational::new(0, 1),
|
||||
&mut table,
|
||||
);
|
||||
assert!(table.is_empty());
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,11 @@ pub fn copy_inputs(
|
||||
// C++ copies per-element too — array elements are covered when the
|
||||
// array family lands).
|
||||
for id in &src_inputs {
|
||||
if !graph.get(dst).map(|e| e.core.has_input(id)).unwrap_or(false) {
|
||||
if !graph
|
||||
.get(dst)
|
||||
.map(|e| e.core.has_input(id))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let value = {
|
||||
@@ -72,16 +76,16 @@ pub fn copy_inputs(
|
||||
};
|
||||
let declared = {
|
||||
let entry = graph.get(src).ok_or(Error::NotFound)?;
|
||||
entry.core.input_data_type(id).unwrap_or(crate::value::ValueType::None)
|
||||
entry
|
||||
.core
|
||||
.input_data_type(id)
|
||||
.unwrap_or(crate::value::ValueType::None)
|
||||
};
|
||||
let value = {
|
||||
// Re-quantize to the destination's declared type so the copy
|
||||
// never stores a mismatched payload.
|
||||
let entry = graph.get(dst).ok_or(Error::NotFound)?;
|
||||
let dst_declared = entry
|
||||
.core
|
||||
.input_data_type(id)
|
||||
.ok_or(Error::NotFound)?;
|
||||
let dst_declared = entry.core.input_data_type(id).ok_or(Error::NotFound)?;
|
||||
if dst_declared == declared {
|
||||
value
|
||||
} else {
|
||||
@@ -294,8 +298,7 @@ pub fn set_value_at_time_command(
|
||||
move || {
|
||||
let mut g = lock_any(&project_undo);
|
||||
if let Some(e) = g.graph.get_mut(node) {
|
||||
e.core
|
||||
.set_standard_value(&input_undo, element, old.clone());
|
||||
e.core.set_standard_value(&input_undo, element, old.clone());
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -44,11 +44,7 @@ pub struct NodeRef {
|
||||
impl NodeRef {
|
||||
/// New reference. `owned` selects whether releasing the last handle
|
||||
/// reference accounts the node in [`crate::ffi::debug_alive_count`].
|
||||
pub fn new(
|
||||
project: Arc<Mutex<Project>>,
|
||||
id: NodeId,
|
||||
owned: bool,
|
||||
) -> NodeRef {
|
||||
pub fn new(project: Arc<Mutex<Project>>, id: NodeId, owned: bool) -> NodeRef {
|
||||
NodeRef {
|
||||
project,
|
||||
id,
|
||||
@@ -112,10 +108,8 @@ impl Project {
|
||||
let (core, behavior) = crate::folder::create("Root");
|
||||
let id = self.graph.add_node(core, behavior);
|
||||
self.root = id;
|
||||
self.settings.insert(
|
||||
SETTING_ROOT.to_string(),
|
||||
id.identity().to_string(),
|
||||
);
|
||||
self.settings
|
||||
.insert(SETTING_ROOT.to_string(), id.identity().to_string());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -147,7 +141,8 @@ impl Project {
|
||||
// Map original id -> copied id, preserving identities where the
|
||||
// copy's arena slots are free (the copy starts empty, so every
|
||||
// node keeps its identity).
|
||||
let mut id_map: std::collections::HashMap<NodeId, NodeId> = std::collections::HashMap::new();
|
||||
let mut id_map: std::collections::HashMap<NodeId, NodeId> =
|
||||
std::collections::HashMap::new();
|
||||
for id in self.graph.node_ids() {
|
||||
let entry = self.graph.get(id).ok_or(Error::NotFound)?;
|
||||
let (core, behavior) = clone_entry(entry);
|
||||
@@ -188,7 +183,11 @@ impl Project {
|
||||
/// Incremental sync of a deep copy after edits (C++
|
||||
/// ProjectCopier::queue_update semantics): applies the recorded
|
||||
/// change set to `copy`.
|
||||
pub fn sync_copy(&self, copy: &mut Project, changes: &[ChangeRecord]) -> crate::error::Result<()> {
|
||||
pub fn sync_copy(
|
||||
&self,
|
||||
copy: &mut Project,
|
||||
changes: &[ChangeRecord],
|
||||
) -> crate::error::Result<()> {
|
||||
use crate::error::Error;
|
||||
// Rebuild the id mapping from the copy (identities are stable
|
||||
// across the deep-copy, so original id -> copy id is identity).
|
||||
@@ -226,8 +225,14 @@ impl Project {
|
||||
copy.graph.disconnect(*from, *to, input, *element);
|
||||
}
|
||||
}
|
||||
ChangeRecord::ValueChanged { node, input, element } => {
|
||||
if let (Some(src), Some(dst)) = (self.graph.get(*node), copy.graph.get_mut(*node)) {
|
||||
ChangeRecord::ValueChanged {
|
||||
node,
|
||||
input,
|
||||
element,
|
||||
} => {
|
||||
if let (Some(src), Some(dst)) =
|
||||
(self.graph.get(*node), copy.graph.get_mut(*node))
|
||||
{
|
||||
let v = src.core.standard_value(input, *element);
|
||||
dst.core.set_standard_value(input, *element, v);
|
||||
}
|
||||
@@ -327,7 +332,9 @@ impl Project {
|
||||
|
||||
/// Clone a node entry into independently-owned parts (deep copy of the
|
||||
/// core data; the behavior is re-created via [`NodeBehavior::duplicate`]).
|
||||
fn clone_entry(entry: &crate::graph::NodeEntry) -> (crate::node::NodeCore, Box<dyn crate::node::NodeBehavior>) {
|
||||
fn clone_entry(
|
||||
entry: &crate::graph::NodeEntry,
|
||||
) -> (crate::node::NodeCore, Box<dyn crate::node::NodeBehavior>) {
|
||||
let core = entry.core.clone();
|
||||
let behavior = entry
|
||||
.behavior
|
||||
|
||||
@@ -72,8 +72,8 @@ impl SequenceBehavior {
|
||||
/// `ViewerOutput::set_default_parameters()`; the config lookups use
|
||||
/// oakcommon's defaults when the config module is absent).
|
||||
pub fn set_default_parameters(&mut self) {
|
||||
let width = crate::bridge::common::config_get_int("DefaultSequenceWidth", "", 1920)
|
||||
.unwrap_or(1920);
|
||||
let width =
|
||||
crate::bridge::common::config_get_int("DefaultSequenceWidth", "", 1920).unwrap_or(1920);
|
||||
let height = crate::bridge::common::config_get_int("DefaultSequenceHeight", "", 1080)
|
||||
.unwrap_or(1080);
|
||||
let sample_rate =
|
||||
@@ -100,7 +100,14 @@ impl SequenceBehavior {
|
||||
|
||||
/// Recompute the cached lengths from the track lists (C++
|
||||
/// `ViewerOutput::verify_length()`).
|
||||
pub fn verify_length(&mut self, lengths: (oakcore_rs::Rational, oakcore_rs::Rational, oakcore_rs::Rational)) {
|
||||
pub fn verify_length(
|
||||
&mut self,
|
||||
lengths: (
|
||||
oakcore_rs::Rational,
|
||||
oakcore_rs::Rational,
|
||||
oakcore_rs::Rational,
|
||||
),
|
||||
) {
|
||||
let (video, audio, overall) = lengths;
|
||||
self.last_length = overall;
|
||||
let _ = (video, audio);
|
||||
|
||||
@@ -236,7 +236,9 @@ pub fn string_to_value(declared: ValueType, text: &str) -> NodeValue {
|
||||
ValueType::Vec2 | ValueType::Vec3 | ValueType::Vec4 | ValueType::Color => {
|
||||
let parts: Vec<f64> = text.split(':').map(|p| p.parse().unwrap_or(0.0)).collect();
|
||||
match declared {
|
||||
ValueType::Vec2 => NodeValue::Vec2([parts[0], parts.get(1).copied().unwrap_or(0.0)]),
|
||||
ValueType::Vec2 => {
|
||||
NodeValue::Vec2([parts[0], parts.get(1).copied().unwrap_or(0.0)])
|
||||
}
|
||||
ValueType::Vec3 => NodeValue::Vec3([
|
||||
parts[0],
|
||||
parts.get(1).copied().unwrap_or(0.0),
|
||||
@@ -406,9 +408,7 @@ fn save_immediate(writer: &mut dyn XmlWrite, core: &NodeCore, id: &str, element:
|
||||
.keyframe_track(id, element)
|
||||
.map(|t| !t.keys().is_empty())
|
||||
.unwrap_or(false);
|
||||
let declared = core
|
||||
.input_data_type(id)
|
||||
.unwrap_or(ValueType::None);
|
||||
let declared = core.input_data_type(id).unwrap_or(ValueType::None);
|
||||
|
||||
if keyframable {
|
||||
writer.text_element("keyframing", if keyframing { "1" } else { "0" });
|
||||
@@ -442,10 +442,7 @@ fn save_immediate(writer: &mut dyn XmlWrite, core: &NodeCore, id: &str, element:
|
||||
writer.attribute("inhandley", &format!("{}", key.bezier_in.1));
|
||||
writer.attribute("outhandlex", &format!("{}", key.bezier_out.0));
|
||||
writer.attribute("outhandley", &format!("{}", key.bezier_out.1));
|
||||
writer_text_chars(
|
||||
writer,
|
||||
&value_to_string(declared, &key.value, true),
|
||||
);
|
||||
writer_text_chars(writer, &value_to_string(declared, &key.value, true));
|
||||
writer.end_element(); // key
|
||||
}
|
||||
writer.end_element(); // track
|
||||
@@ -528,7 +525,13 @@ fn load_project_body(reader: &mut dyn XmlRead, project: &mut Project) -> crate::
|
||||
"nodes" => {
|
||||
while reader.next_start_element() {
|
||||
if reader.name() == "node" {
|
||||
let id = load_node(reader, &mut project.graph, &mut id_map, &mut connections, &mut links)?;
|
||||
let id = load_node(
|
||||
reader,
|
||||
&mut project.graph,
|
||||
&mut id_map,
|
||||
&mut connections,
|
||||
&mut links,
|
||||
)?;
|
||||
if project.root == NodeId::INVALID {
|
||||
// The first node is the root folder when the
|
||||
// project has no explicit root setting.
|
||||
@@ -553,7 +556,10 @@ fn load_project_body(reader: &mut dyn XmlRead, project: &mut Project) -> crate::
|
||||
// Resolve connections.
|
||||
for (out_identity, in_id, input_id, element) in connections {
|
||||
if let Some(out_id) = id_map.get(&out_identity) {
|
||||
project.graph.connect(*out_id, in_id, &input_id, element).ok();
|
||||
project
|
||||
.graph
|
||||
.connect(*out_id, in_id, &input_id, element)
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
// Resolve links (the writer emits one entry per direction; linking
|
||||
@@ -594,20 +600,19 @@ fn load_node(
|
||||
|
||||
// Instantiate the node type; folders and unknown types fall back to
|
||||
// an empty folder-ish core.
|
||||
let (mut core, behavior): (NodeCore, Box<dyn crate::node::NodeBehavior>) = if type_id
|
||||
== "org.olivevideoeditor.Olive.folder"
|
||||
{
|
||||
crate::folder::create("Folder")
|
||||
} else {
|
||||
match crate::factory::Factory::global().find(&type_id) {
|
||||
Some(meta) => (meta.create)(),
|
||||
None => {
|
||||
// Unknown type: skip the element body.
|
||||
reader.skip_current_element();
|
||||
return Err(Error::Failed(format!("unknown node type '{}'", type_id)));
|
||||
let (mut core, behavior): (NodeCore, Box<dyn crate::node::NodeBehavior>) =
|
||||
if type_id == "org.olivevideoeditor.Olive.folder" {
|
||||
crate::folder::create("Folder")
|
||||
} else {
|
||||
match crate::factory::Factory::global().find(&type_id) {
|
||||
Some(meta) => (meta.create)(),
|
||||
None => {
|
||||
// Unknown type: skip the element body.
|
||||
reader.skip_current_element();
|
||||
return Err(Error::Failed(format!("unknown node type '{}'", type_id)));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// The node enters the graph before its body is parsed so deferred
|
||||
// connections/links can reference it by id.
|
||||
@@ -713,7 +718,13 @@ fn load_input_element(reader: &mut dyn XmlRead, core: &mut NodeCore, input_id: &
|
||||
}
|
||||
|
||||
/// Parse one immediate (standard values + keyframes).
|
||||
fn load_immediate(reader: &mut dyn XmlRead, core: &mut NodeCore, input_id: &str, element: i32, declared: ValueType) {
|
||||
fn load_immediate(
|
||||
reader: &mut dyn XmlRead,
|
||||
core: &mut NodeCore,
|
||||
input_id: &str,
|
||||
element: i32,
|
||||
declared: ValueType,
|
||||
) {
|
||||
let mut keyframing = false;
|
||||
let mut standard_tracks: Vec<NodeValue> = Vec::new();
|
||||
let mut keyframe_tracks: Vec<Vec<Keyframe>> = Vec::new();
|
||||
@@ -798,7 +809,8 @@ fn load_immediate(reader: &mut dyn XmlRead, core: &mut NodeCore, input_id: &str,
|
||||
}
|
||||
} else {
|
||||
// No keyframes: drop any pre-existing track.
|
||||
core.keyframes.retain(|(i, e, _)| !(i == input_id && *e == element));
|
||||
core.keyframes
|
||||
.retain(|(i, e, _)| !(i == input_id && *e == element));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -191,7 +191,11 @@ impl TrackBehavior {
|
||||
|
||||
/// Block strictly containing `time` (in < time < out; C++
|
||||
/// `block_containing_time`).
|
||||
pub fn block_containing_time(&self, time: oakcore_rs::Rational, blocks: &dyn BlockRange) -> Option<NodeId> {
|
||||
pub fn block_containing_time(
|
||||
&self,
|
||||
time: oakcore_rs::Rational,
|
||||
blocks: &dyn BlockRange,
|
||||
) -> Option<NodeId> {
|
||||
self.blocks
|
||||
.iter()
|
||||
.find(|b| blocks.contains_strict(**b, time))
|
||||
@@ -200,7 +204,11 @@ impl TrackBehavior {
|
||||
|
||||
/// Block visible at `time` (in <= time < out; C++
|
||||
/// `visible_block_at_time`).
|
||||
pub fn visible_block_at_time(&self, time: oakcore_rs::Rational, blocks: &dyn BlockRange) -> Option<NodeId> {
|
||||
pub fn visible_block_at_time(
|
||||
&self,
|
||||
time: oakcore_rs::Rational,
|
||||
blocks: &dyn BlockRange,
|
||||
) -> Option<NodeId> {
|
||||
self.blocks
|
||||
.iter()
|
||||
.find(|b| blocks.contains(**b, time))
|
||||
@@ -210,10 +218,7 @@ impl TrackBehavior {
|
||||
/// Whether the [in, out) range holds no block or only a gap (C++
|
||||
/// `is_range_free`).
|
||||
pub fn is_range_free(&self, range: oakcore_rs::TimeRange, blocks: &dyn BlockRange) -> bool {
|
||||
!self
|
||||
.blocks
|
||||
.iter()
|
||||
.any(|b| blocks.overlaps(*b, range))
|
||||
!self.blocks.iter().any(|b| blocks.overlaps(*b, range))
|
||||
}
|
||||
|
||||
/// Total length (end of the last block; C++ `Track::get_length`).
|
||||
|
||||
@@ -156,15 +156,15 @@ impl Traverser {
|
||||
let entry = graph.get(node).ok_or(Error::NotFound)?;
|
||||
// The behavior writes outputs; the default no-op leaves the
|
||||
// table empty (C++ `Node::value` default).
|
||||
entry.behavior.value(&entry.core, &row, request.time, &mut table);
|
||||
entry
|
||||
.behavior
|
||||
.value(&entry.core, &row, request.time, &mut table);
|
||||
hooks.resolve(node, &row, &mut table);
|
||||
|
||||
tables.insert(node, table);
|
||||
}
|
||||
|
||||
Ok(tables
|
||||
.remove(&request.root)
|
||||
.unwrap_or_default())
|
||||
Ok(tables.remove(&request.root).unwrap_or_default())
|
||||
}
|
||||
|
||||
/// Invalidate walk: mark downstream caches dirty after an input
|
||||
|
||||
+10
-12
@@ -224,10 +224,10 @@ impl SampleBuffer {
|
||||
SampleFormat::S16Planar | SampleFormat::S16 => {
|
||||
i16::from_le_bytes([raw[0], raw[1]]) as f64
|
||||
}
|
||||
SampleFormat::S32Planar | SampleFormat::S32 | SampleFormat::F32Planar
|
||||
| SampleFormat::F32 => {
|
||||
f32::from_le_bytes([raw[0], raw[1], raw[2], raw[3]]) as f64
|
||||
}
|
||||
SampleFormat::S32Planar
|
||||
| SampleFormat::S32
|
||||
| SampleFormat::F32Planar
|
||||
| SampleFormat::F32 => f32::from_le_bytes([raw[0], raw[1], raw[2], raw[3]]) as f64,
|
||||
SampleFormat::S64Planar | SampleFormat::S64 => {
|
||||
i64::from_le_bytes(raw.try_into().unwrap_or([0; 8])) as f64
|
||||
}
|
||||
@@ -496,9 +496,7 @@ impl NodeValue {
|
||||
pub fn lerp(&self, other: &NodeValue, t: f64) -> NodeValue {
|
||||
match (self, other) {
|
||||
(NodeValue::Float(a), NodeValue::Float(b)) => NodeValue::Float(lerp_f(a, b, t)),
|
||||
(NodeValue::Color(a), NodeValue::Color(b)) => {
|
||||
NodeValue::Color(lerp_arr4(a, b, t))
|
||||
}
|
||||
(NodeValue::Color(a), NodeValue::Color(b)) => NodeValue::Color(lerp_arr4(a, b, t)),
|
||||
(NodeValue::Vec2(a), NodeValue::Vec2(b)) => NodeValue::Vec2(lerp_arr2(a, b, t)),
|
||||
(NodeValue::Vec3(a), NodeValue::Vec3(b)) => NodeValue::Vec3(lerp_arr3(a, b, t)),
|
||||
(NodeValue::Vec4(a), NodeValue::Vec4(b)) => NodeValue::Vec4(lerp_arr4(a, b, t)),
|
||||
@@ -536,10 +534,7 @@ fn lerp_f(a: &f64, b: &f64, t: f64) -> f64 {
|
||||
}
|
||||
|
||||
fn lerp_arr2(a: &[f64; 2], b: &[f64; 2], t: f64) -> [f64; 2] {
|
||||
[
|
||||
lerp_f(&a[0], &b[0], t),
|
||||
lerp_f(&a[1], &b[1], t),
|
||||
]
|
||||
[lerp_f(&a[0], &b[0], t), lerp_f(&a[1], &b[1], t)]
|
||||
}
|
||||
|
||||
fn lerp_arr3(a: &[f64; 3], b: &[f64; 3], t: f64) -> [f64; 3] {
|
||||
@@ -770,7 +765,10 @@ impl OakNodeValue {
|
||||
/// (C++ `value_from_variant`). String-carried declared types fail with
|
||||
/// [`Error::Invalid`]; types without a POD representation fail with
|
||||
/// [`Error::Failed`].
|
||||
pub fn from_node_value(declared: ValueType, v: &NodeValue) -> crate::error::Result<OakNodeValue> {
|
||||
pub fn from_node_value(
|
||||
declared: ValueType,
|
||||
v: &NodeValue,
|
||||
) -> crate::error::Result<OakNodeValue> {
|
||||
use crate::error::Error;
|
||||
if declared.is_string() {
|
||||
return Err(Error::Invalid);
|
||||
|
||||
Reference in New Issue
Block a user