Files
oak-editor/crates/oaknode/src/nodes/valuenode.rs
T
Mike-Solar 18ff60f147 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)
2026-08-11 23:04:48 +08:00

253 lines
7.6 KiB
Rust

// Oak Video Editor - Non-Linear Video Editor
// Copyright (C) 2026 Oak Team
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//! Constant-value generator node (C++
//! `src/node/src/input/value/valuenode.{h,cpp}`, `olive::ValueNode`).
use crate::factory::NodeMeta;
use crate::node::{Category, NodeBehavior, NodeCore};
use crate::value::ValueType;
/// Type selector input id (C++ `k_type_input`). Type: combo; default
/// `0` (the first entry of [`SUPPORTED_TYPES`]); flags: not-connectable,
/// not-keyframable. Combo strings are the pretty data-type names of the
/// supported types (built in `retranslate()`).
pub const TYPE_INPUT: &str = "type_in";
/// Value input id (C++ `k_value_input`). Type: initially
/// `SUPPORTED_TYPES[0]` (float), switched by `input_value_changed` when
/// `type_in` changes; default: empty variant; flags: not-connectable.
pub const VALUE_INPUT: &str = "value_in";
/// Selectable value types (C++ `k_supported_types`). The C++ list is:
/// float, int, rational, vec2, vec3, vec4, color, text, matrix, font,
/// boolean. The Rust [`ValueType`] enum has no `Matrix` or `Font`
/// variants, so those two entries are omitted here (a behavioral gap to
/// resolve when matrix/font values land in `value.rs`).
pub const SUPPORTED_TYPES: &[ValueType] = &[
ValueType::Float,
ValueType::Int,
ValueType::Rational,
ValueType::Vec2,
ValueType::Vec3,
ValueType::Vec4,
ValueType::Color,
ValueType::Text,
ValueType::Boolean,
];
/// Value node. Holds a single typed constant that can be connected to
/// other nodes' inputs. The C++ class has no own members (its state
/// lives entirely in the two inputs), so this is a unit-like struct.
pub struct ValueNode;
impl NodeBehavior for ValueNode {
/// Human-readable name (C++ `name()`).
fn name(&self) -> &str {
"Value"
}
/// Stable type id (C++ `id()`).
fn type_id(&self) -> &str {
"org.olivevideoeditor.Olive.value"
}
/// Categories (C++ `category()` returns
/// `{ k_category_generator }`).
fn categories(&self) -> &[Category] {
&[Category::Generator]
}
/// Description (C++ `description()`).
fn description(&self) -> &str {
"Create a single value that can be connected to various other inputs."
}
/// Localized input names (C++ `retranslate()`): `type_in` ->
/// "Type", `value_in` -> "Value". The C++ override also sets the
/// `type_in` combo strings to the pretty names of the supported
/// types — that part has no trait surface and is noted here only.
fn input_name<'a>(&self, id: &'a str) -> &'a str {
match id {
TYPE_INPUT => "Type",
VALUE_INPUT => "Value",
_ => id,
}
}
/// Evaluate outputs (C++ `value()`): pushes the `value_in` value
/// onto the table unchanged.
fn value(
&self,
core: &NodeCore,
inputs: &crate::value::NodeValueRow,
time: oakcore_rs::Rational,
table: &mut crate::value::NodeValueTable,
) {
let _ = inputs;
let v = core.value_at_time(VALUE_INPUT, -1, time);
table.push(v.value_type(), v, None);
}
/// Input value changed (C++ `InputValueChangedEvent()`): when
/// `type_in` changes, sets the data type of `value_in` to
/// `SUPPORTED_TYPES[type index]`; then defers to the base-class
/// behavior.
fn input_value_changed(&mut self, core: &mut NodeCore, input: &str, element: i32) {
if input == TYPE_INPUT && element == -1 {
let idx = core.standard_value(TYPE_INPUT, -1).to_double() as usize;
if let Some(ty) = SUPPORTED_TYPES.get(idx) {
if let Some(value_in) = core.get_input_mut(VALUE_INPUT) {
value_in.value_type = *ty;
}
}
}
}
/// Deep copy (C++ `copy()` via `NODE_DEFAULT_FUNCTIONS`).
fn duplicate(&self, _core: &NodeCore) -> Option<Box<dyn NodeBehavior>> {
Some(Box::new(ValueNode))
}
}
/// Constructor (C++ `ValueNode::ValueNode()`): adds `type_in` and
/// `value_in` with the defaults, flags and properties documented on the
/// constants.
pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
let mut core = NodeCore::new();
let mut type_input = crate::input::Input::new(
TYPE_INPUT,
ValueType::Combo,
crate::value::NodeValue::Combo(0),
);
type_input.flags |= crate::input::flags::NOT_CONNECTABLE | crate::input::flags::NOT_KEYFRAMABLE;
core.add_input(type_input);
let mut value_input = crate::input::Input::new(
VALUE_INPUT,
SUPPORTED_TYPES[0],
crate::value::NodeValue::None,
);
value_input.flags |= crate::input::flags::NOT_CONNECTABLE;
core.add_input(value_input);
(core, Box::new(ValueNode))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::node::NodeBehavior;
use crate::value::{NodeValue, NodeValueTable, ValueType};
use oakcore_rs::Rational;
#[test]
fn input_names() {
let n = ValueNode;
assert_eq!(n.input_name(TYPE_INPUT), "Type");
assert_eq!(n.input_name(VALUE_INPUT), "Value");
}
#[test]
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(TYPE_INPUT).unwrap().flags & crate::input::flags::NOT_CONNECTABLE,
crate::input::flags::NOT_CONNECTABLE
);
}
#[test]
fn value_pushes_standard_value() {
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,
);
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),
});
let mut table = NodeValueTable::default();
behavior.value(
&core,
&crate::value::NodeValueRow::default(),
Rational::new(10, 1),
&mut table,
);
assert_eq!(table.get(ValueType::Float), Some(&NodeValue::Float(9.0)));
}
#[test]
fn input_value_changed_switches_type() {
let (mut core, behavior) = create();
let mut behavior = behavior;
// 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
);
// 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
);
}
#[test]
fn duplicate_clones() {
let (core, behavior) = create();
let dup = behavior.duplicate(&core).unwrap();
assert_eq!(dup.name(), "Value");
}
}
/// Register this node type (C++ `k_value_node` in
/// `factory.cpp::create_from_factory_index`).
pub fn register(meta: &mut Vec<NodeMeta>) {
meta.push(NodeMeta {
type_id: "org.olivevideoeditor.Olive.value",
name: "Value",
categories: &[Category::Generator],
create,
});
}