refactor: drop internal bridge/ffi layers; exporter family lands
Single-lib cleanup: the per-crate src/bridge/ and src/ffi.rs layers are gone (oakundo/oakcommon/oaknode/oaktimeline/oakcodec/oakaudio/ oakrender/oaktask/oakplugin/oakstorage); cross-crate calls are plain Rust, CHandle marshalling shrinks to the oakengine boundary, and tests call the Rust APIs directly (pure C-ABI wrapper tests removed where the domain layer already covers the behavior). exporter.h family implemented: oakengine_export_render (CLI contract), oakengine_export_render_with_params (was a stub), last_error and progress callback; synchronous path reuses task_create_export + start_sync. Fixes on the way: oaktask video ticket self-deadlock, audio params dropped on the export path, codec encoder AAC slicing and H.264 time base. Real-mp4 tests cover both entry points, progress and the illegal-argument matrix. Also: oakstorage session maps null project handles to None (version- info path), configstore test double literal 3.14 -> 3.15 (clippy PI lint), oakaudio output callback scratch buffer + env-aware P1 test, cli media round-trip test uses a generated 16-frame clip (no more minute-long debug runs).
This commit is contained in:
@@ -1,306 +0,0 @@
|
||||
// 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/>.
|
||||
|
||||
// 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/>.
|
||||
|
||||
//! oakcommon C ABI imports (XML reader/writer + config), mirroring
|
||||
//! `include/common/xmlutils.h` and `include/common/config.h`.
|
||||
|
||||
use std::ffi::{c_char, c_int};
|
||||
|
||||
use crate::handle::CHandle;
|
||||
|
||||
/// Direct call into the `oakcommon` crate (single-lib unification).
|
||||
pub fn oakcommon_xml_reader_init(data: *const c_char) -> CHandle {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oakcommon_xml_reader_init(data)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_init(data) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oakcommon` crate (single-lib unification).
|
||||
pub fn oakcommon_xml_reader_free(reader: *mut CHandle) {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oakcommon_xml_reader_free(reader)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_free(reader) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oakcommon` crate (single-lib unification).
|
||||
pub fn oakcommon_xml_reader_skip_current_element(reader: CHandle) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oakcommon_xml_reader_skip_current_element(reader)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_skip_current_element(reader) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oakcommon` crate (single-lib unification). The
|
||||
/// real ABI advances the reader and writes a 1/0 `found` flag (the name is
|
||||
/// read with [`oakcommon_xml_reader_name`]).
|
||||
pub fn oakcommon_xml_reader_read_next_start_element(reader: CHandle, found: *mut c_int) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oakcommon_xml_reader_read_next_start_element(reader, found)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe {
|
||||
oakcommon::ffi::xmlutils::oakcommon_xml_reader_read_next_start_element(reader, found)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oakcommon` crate (single-lib unification).
|
||||
pub fn oakcommon_xml_reader_name(reader: CHandle, name: *mut c_char, buf_size: c_int) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oakcommon_xml_reader_name(reader, name, buf_size)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_name(reader, name, buf_size) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oakcommon` crate (single-lib unification).
|
||||
pub fn oakcommon_xml_reader_read_element_text(
|
||||
reader: CHandle,
|
||||
text: *mut c_char,
|
||||
buf_size: c_int,
|
||||
) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oakcommon_xml_reader_read_element_text(reader, text, buf_size)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe {
|
||||
oakcommon::ffi::xmlutils::oakcommon_xml_reader_read_element_text(reader, text, buf_size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oakcommon` crate (single-lib unification).
|
||||
pub fn oakcommon_xml_reader_attribute_count(reader: CHandle, count: *mut c_int) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oakcommon_xml_reader_attribute_count(reader, count)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_attribute_count(reader, count) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oakcommon` crate (single-lib unification).
|
||||
pub fn oakcommon_xml_reader_attribute_name(
|
||||
reader: CHandle,
|
||||
index: c_int,
|
||||
name: *mut c_char,
|
||||
buf_size: c_int,
|
||||
) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oakcommon_xml_reader_attribute_name(reader, index, name, buf_size)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe {
|
||||
oakcommon::ffi::xmlutils::oakcommon_xml_reader_attribute_name(
|
||||
reader, index, name, buf_size,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oakcommon` crate (single-lib unification).
|
||||
pub fn oakcommon_xml_reader_attribute_value(
|
||||
reader: CHandle,
|
||||
index: c_int,
|
||||
value: *mut c_char,
|
||||
buf_size: c_int,
|
||||
) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oakcommon_xml_reader_attribute_value(reader, index, value, buf_size)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe {
|
||||
oakcommon::ffi::xmlutils::oakcommon_xml_reader_attribute_value(
|
||||
reader, index, value, buf_size,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oakcommon` crate (single-lib unification).
|
||||
pub fn oakcommon_xml_reader_has_error(reader: CHandle, has_error: *mut c_int) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oakcommon_xml_reader_has_error(reader, has_error)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_reader_has_error(reader, has_error) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oakcommon` crate (single-lib unification).
|
||||
pub fn oakcommon_xml_writer_init() -> CHandle {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oakcommon_xml_writer_init()
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_writer_init() }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oakcommon` crate (single-lib unification).
|
||||
pub fn oakcommon_xml_writer_free(writer: *mut CHandle) {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oakcommon_xml_writer_free(writer)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_writer_free(writer) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oakcommon` crate (single-lib unification).
|
||||
pub fn oakcommon_xml_writer_write_start_element(writer: CHandle, name: *const c_char) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oakcommon_xml_writer_write_start_element(writer, name)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_writer_write_start_element(writer, name) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oakcommon` crate (single-lib unification).
|
||||
pub fn oakcommon_xml_writer_write_end_element(writer: CHandle) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oakcommon_xml_writer_write_end_element(writer)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_writer_write_end_element(writer) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oakcommon` crate (single-lib unification).
|
||||
pub fn oakcommon_xml_writer_write_end_document(writer: CHandle) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oakcommon_xml_writer_write_end_document(writer)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_writer_write_end_document(writer) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oakcommon` crate (single-lib unification).
|
||||
pub fn oakcommon_xml_writer_write_attribute(
|
||||
writer: CHandle,
|
||||
key: *const c_char,
|
||||
value: *const c_char,
|
||||
) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oakcommon_xml_writer_write_attribute(writer, key, value)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe {
|
||||
oakcommon::ffi::xmlutils::oakcommon_xml_writer_write_attribute(writer, key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oakcommon` crate (single-lib unification).
|
||||
pub fn oakcommon_xml_writer_write_characters(writer: CHandle, text: *const c_char) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oakcommon_xml_writer_write_characters(writer, text)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oakcommon::ffi::xmlutils::oakcommon_xml_writer_write_characters(writer, text) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oakcommon` crate (single-lib unification).
|
||||
pub fn oakcommon_xml_writer_write_text_element(
|
||||
writer: CHandle,
|
||||
name: *const c_char,
|
||||
text: *const c_char,
|
||||
) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oakcommon_xml_writer_write_text_element(writer, name, text)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe {
|
||||
oakcommon::ffi::xmlutils::oakcommon_xml_writer_write_text_element(writer, name, text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oakcommon` crate (single-lib unification).
|
||||
pub fn oakcommon_config_get_int(group: *const c_char, key: *const c_char, default: c_int) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oakcommon_config_get_int(group, key, default)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oakcommon::ffi::config::oakcommon_config_get_int(group, key, default) }
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
// 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/>.
|
||||
|
||||
//! C ABI imports from other oak modules. Signatures mirror the public
|
||||
//! headers verbatim; they are resolved at link time against the oaknode,
|
||||
//! oakundo and oakcommon DLLs.
|
||||
//!
|
||||
//! Timeline never reimplements C++ internals: every cross-module access
|
||||
//! goes through these frozen C ABIs (see README.md "no replication").
|
||||
|
||||
pub mod common;
|
||||
pub mod node;
|
||||
pub mod undo;
|
||||
|
||||
/// In-crate mock implementations of the bridge surface, compiled for tests
|
||||
/// (`cfg(test)` or the `test-stubs` feature); the bridge fns route to them
|
||||
/// via their `#[cfg(any(test, feature = "test-stubs"))]` variants. Plain
|
||||
/// Rust (no `#[no_mangle]`), so they coexist with the real
|
||||
/// oaknode/oakundo/oakcommon rlibs linked in the same test binary.
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
pub mod teststubs;
|
||||
@@ -1,856 +0,0 @@
|
||||
// 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/>.
|
||||
|
||||
// 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/>.
|
||||
|
||||
//! oaknode C ABI imports (`include/node/{block,node,sequence,track,project}.h`).
|
||||
//! Timeline reads and mutates the block/track/sequence graph exclusively
|
||||
//! through these frozen functions — it never reimplements node internals.
|
||||
//!
|
||||
//! All time quantities cross the C ABI as `int` numerator/denominator pairs
|
||||
//! (the ABI is `int`; the crate's internal `Rational` is `i64`). Converting
|
||||
//! between the two is the bridge's responsibility.
|
||||
|
||||
use std::ffi::{c_char, c_int};
|
||||
|
||||
use crate::handle::CHandle;
|
||||
|
||||
/// A timeline block (`OakNodeBlock`) — a clip or a gap. Opaque handle.
|
||||
pub type OakNodeBlock = CHandle;
|
||||
/// A track (`OakNodeTrack`). Opaque handle.
|
||||
pub type OakNodeTrack = CHandle;
|
||||
/// A sequence/timeline (`OakNodeSequence`). Opaque handle.
|
||||
pub type OakNodeSequence = CHandle;
|
||||
/// A generic node (`OakNodeNode`). Opaque handle.
|
||||
pub type OakNodeNode = CHandle;
|
||||
/// A project (`OakNodeProject`). Opaque handle.
|
||||
pub type OakNodeProject = CHandle;
|
||||
/// A track list (`OakNodeTrackList`). Opaque handle.
|
||||
pub type OakNodeTrackList = CHandle;
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_block_clip_create() -> OakNodeBlock {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_block_clip_create()
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::block::oaknode_block_clip_create() }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_block_gap_create() -> OakNodeBlock {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_block_gap_create()
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::block::oaknode_block_gap_create() }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_block_as_node(block: OakNodeBlock) -> OakNodeNode {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_block_as_node(block)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::block::oaknode_block_as_node(block) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_block_get_in(
|
||||
block: OakNodeBlock,
|
||||
numerator: *mut c_int,
|
||||
denominator: *mut c_int,
|
||||
) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_block_get_in(block, numerator, denominator)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::block::oaknode_block_get_in(block, numerator, denominator) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_block_get_out(
|
||||
block: OakNodeBlock,
|
||||
numerator: *mut c_int,
|
||||
denominator: *mut c_int,
|
||||
) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_block_get_out(block, numerator, denominator)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::block::oaknode_block_get_out(block, numerator, denominator) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_block_get_length(
|
||||
block: OakNodeBlock,
|
||||
numerator: *mut c_int,
|
||||
denominator: *mut c_int,
|
||||
) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_block_get_length(block, numerator, denominator)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::block::oaknode_block_get_length(block, numerator, denominator) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_block_set_length_and_media_out(
|
||||
block: OakNodeBlock,
|
||||
numerator: c_int,
|
||||
denominator: c_int,
|
||||
) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_block_set_length_and_media_out(block, numerator, denominator)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe {
|
||||
oaknode::ffi::block::oaknode_block_set_length_and_media_out(
|
||||
block,
|
||||
numerator,
|
||||
denominator,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_block_set_length_and_media_in(
|
||||
block: OakNodeBlock,
|
||||
numerator: c_int,
|
||||
denominator: c_int,
|
||||
) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_block_set_length_and_media_in(block, numerator, denominator)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe {
|
||||
oaknode::ffi::block::oaknode_block_set_length_and_media_in(
|
||||
block,
|
||||
numerator,
|
||||
denominator,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_block_set_in(block: OakNodeBlock, numerator: c_int, denominator: c_int) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_block_set_in(block, numerator, denominator)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::block::oaknode_block_set_in(block, numerator, denominator) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_block_get_enabled(block: OakNodeBlock, enabled: *mut c_int) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_block_get_enabled(block, enabled)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::block::oaknode_block_get_enabled(block, enabled) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_block_set_enabled(block: OakNodeBlock, enabled: c_int) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_block_set_enabled(block, enabled)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::block::oaknode_block_set_enabled(block, enabled) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_block_get_previous(block: OakNodeBlock, out: *mut OakNodeBlock) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_block_get_previous(block, out)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::block::oaknode_block_get_previous(block, out) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_block_get_next(block: OakNodeBlock, out: *mut OakNodeBlock) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_block_get_next(block, out)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::block::oaknode_block_get_next(block, out) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_block_get_track(block: OakNodeBlock, out: *mut OakNodeTrack) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_block_get_track(block, out)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::block::oaknode_block_get_track(block, out) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_block_link(a: OakNodeBlock, b: OakNodeBlock) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_block_link(a, b)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::block::oaknode_block_link(a, b) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_block_unlink(a: OakNodeBlock, b: OakNodeBlock) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_block_unlink(a, b)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::block::oaknode_block_unlink(a, b) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_track_get_length(
|
||||
track: OakNodeTrack,
|
||||
numerator: *mut c_int,
|
||||
denominator: *mut c_int,
|
||||
) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_track_get_length(track, numerator, denominator)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::track::oaknode_track_get_length(track, numerator, denominator) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_track_get_sequence(track: OakNodeTrack, out: *mut OakNodeSequence) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_track_get_sequence(track, out)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::track::oaknode_track_get_sequence(track, out) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_track_prepend_block(track: OakNodeTrack, block: OakNodeBlock) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_track_prepend_block(track, block)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::track::oaknode_track_prepend_block(track, block) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_track_insert_block_after(
|
||||
track: OakNodeTrack,
|
||||
block: OakNodeBlock,
|
||||
before: OakNodeBlock,
|
||||
) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_track_insert_block_after(track, block, before)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::track::oaknode_track_insert_block_after(track, block, before) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_track_ripple_remove_block(track: OakNodeTrack, block: OakNodeBlock) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_track_ripple_remove_block(track, block)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::track::oaknode_track_ripple_remove_block(track, block) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_track_replace_block(
|
||||
track: OakNodeTrack,
|
||||
old_block: OakNodeBlock,
|
||||
new_block: OakNodeBlock,
|
||||
) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_track_replace_block(track, old_block, new_block)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::track::oaknode_track_replace_block(track, old_block, new_block) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_sequence_as_node(sequence: OakNodeSequence) -> OakNodeNode {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_sequence_as_node(sequence)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::sequence::oaknode_sequence_as_node(sequence) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_sequence_from_node(node: OakNodeNode) -> OakNodeSequence {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_sequence_from_node(node)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::sequence::oaknode_sequence_from_node(node) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_node_get_project(node: OakNodeNode, out: *mut OakNodeProject) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_node_get_project(node, out)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::node::oaknode_node_get_project(node, out) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_node_output_connection_count(node: OakNodeNode, out_count: *mut c_int) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_node_output_connection_count(node, out_count)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::node::oaknode_node_output_connection_count(node, out_count) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_node_get_markers(node: OakNodeNode, out: *mut crate::handle::CHandle) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_node_get_markers(node, out)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::node::oaknode_node_get_markers(node, out as *mut std::ffi::c_void) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_node_get_work_area(node: OakNodeNode, out: *mut crate::handle::CHandle) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_node_get_work_area(node, out)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe {
|
||||
oaknode::ffi::node::oaknode_node_get_work_area(node, out as *mut std::ffi::c_void)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_project_add_node(project: OakNodeProject, node: OakNodeNode) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_project_add_node(project, node)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::project::oaknode_project_add_node(project, node) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_project_remove_node(project: OakNodeProject, node: OakNodeNode) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_project_remove_node(project, node)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::project::oaknode_project_remove_node(project, node) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_command_create_remove_node(node: OakNodeNode) -> crate::handle::CHandle {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_command_create_remove_node(node)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::node::oaknode_command_create_remove_node(node) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_block_get_kind(block: OakNodeBlock, out_kind: *mut c_int) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_block_get_kind(block, out_kind)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::block::oaknode_block_get_kind(block, out_kind) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_block_from_node(node: OakNodeNode) -> OakNodeBlock {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_block_from_node(node)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::block::oaknode_block_from_node(node) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_block_are_linked(a: OakNodeBlock, b: OakNodeBlock, linked: *mut c_int) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_block_are_linked(a, b, linked)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::block::oaknode_block_are_linked(a, b, linked) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_clip_add_cache_passthrough_from(clip: OakNodeBlock, other: OakNodeBlock) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_clip_add_cache_passthrough_from(clip, other)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::block::oaknode_clip_add_cache_passthrough_from(clip, other) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_clip_get_media_in(
|
||||
clip: OakNodeBlock,
|
||||
numerator: *mut c_int,
|
||||
denominator: *mut c_int,
|
||||
) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_clip_get_media_in(clip, numerator, denominator)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::block::oaknode_clip_get_media_in(clip, numerator, denominator) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_clip_set_media_in(
|
||||
clip: OakNodeBlock,
|
||||
numerator: c_int,
|
||||
denominator: c_int,
|
||||
) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_clip_set_media_in(clip, numerator, denominator)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::block::oaknode_clip_set_media_in(clip, numerator, denominator) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_track_create(kind: c_int) -> OakNodeTrack {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_track_create(kind)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::track::oaknode_track_create(kind) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_track_get_locked(track: OakNodeTrack, locked: *mut c_int) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_track_get_locked(track, locked)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::track::oaknode_track_get_locked(track, locked) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_track_set_locked(track: OakNodeTrack, locked: c_int) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_track_set_locked(track, locked)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::track::oaknode_track_set_locked(track, locked) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_track_get_block_count(track: OakNodeTrack, count: *mut c_int) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_track_get_block_count(track, count)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::track::oaknode_track_get_block_count(track, count) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_track_get_block_at(
|
||||
track: OakNodeTrack,
|
||||
index: c_int,
|
||||
out: *mut OakNodeBlock,
|
||||
) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_track_get_block_at(track, index, out)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::track::oaknode_track_get_block_at(track, index, out) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_track_get_block_containing_time(
|
||||
track: OakNodeTrack,
|
||||
numerator: c_int,
|
||||
denominator: c_int,
|
||||
out: *mut OakNodeBlock,
|
||||
) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_track_get_block_containing_time(
|
||||
track,
|
||||
numerator,
|
||||
denominator,
|
||||
out,
|
||||
)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe {
|
||||
oaknode::ffi::track::oaknode_track_get_block_containing_time(
|
||||
track,
|
||||
numerator,
|
||||
denominator,
|
||||
out,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_track_get_nearest_block_before_or_at(
|
||||
track: OakNodeTrack,
|
||||
numerator: c_int,
|
||||
denominator: c_int,
|
||||
out: *mut OakNodeBlock,
|
||||
) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_track_get_nearest_block_before_or_at(
|
||||
track,
|
||||
numerator,
|
||||
denominator,
|
||||
out,
|
||||
)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe {
|
||||
oaknode::ffi::track::oaknode_track_get_nearest_block_before_or_at(
|
||||
track,
|
||||
numerator,
|
||||
denominator,
|
||||
out,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_track_get_nearest_block_after_or_at(
|
||||
track: OakNodeTrack,
|
||||
numerator: c_int,
|
||||
denominator: c_int,
|
||||
out: *mut OakNodeBlock,
|
||||
) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_track_get_nearest_block_after_or_at(
|
||||
track,
|
||||
numerator,
|
||||
denominator,
|
||||
out,
|
||||
)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe {
|
||||
oaknode::ffi::track::oaknode_track_get_nearest_block_after_or_at(
|
||||
track,
|
||||
numerator,
|
||||
denominator,
|
||||
out,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_tracklist_get_type(list: OakNodeTrackList, kind: *mut c_int) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_tracklist_get_type(list, kind)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::track::oaknode_tracklist_get_type(list, kind) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_tracklist_get_track_count(list: OakNodeTrackList, count: *mut c_int) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_tracklist_get_track_count(list, count)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::track::oaknode_tracklist_get_track_count(list, count) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_tracklist_get_track_at(
|
||||
list: OakNodeTrackList,
|
||||
index: c_int,
|
||||
out: *mut OakNodeTrack,
|
||||
) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_tracklist_get_track_at(list, index, out)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::track::oaknode_tracklist_get_track_at(list, index, out) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_tracklist_array_append(list: OakNodeTrackList) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_tracklist_array_append(list)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::track::oaknode_tracklist_array_append(list) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_tracklist_array_remove_last(list: OakNodeTrackList) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_tracklist_array_remove_last(list)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::track::oaknode_tracklist_array_remove_last(list) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_sequence_get_track_list(
|
||||
sequence: OakNodeSequence,
|
||||
kind: c_int,
|
||||
out: *mut OakNodeTrackList,
|
||||
) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_sequence_get_track_list(sequence, kind, out)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::sequence::oaknode_sequence_get_track_list(sequence, kind, out) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_sequence_get_all_track_count(sequence: OakNodeSequence, count: *mut c_int) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_sequence_get_all_track_count(sequence, count)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::sequence::oaknode_sequence_get_all_track_count(sequence, count) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_sequence_get_all_track_at(
|
||||
sequence: OakNodeSequence,
|
||||
index: c_int,
|
||||
out: *mut OakNodeTrack,
|
||||
) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_sequence_get_all_track_at(sequence, index, out)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::sequence::oaknode_sequence_get_all_track_at(sequence, index, out) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_node_connect(
|
||||
output_node: OakNodeNode,
|
||||
input_node: OakNodeNode,
|
||||
input_id: *const c_char,
|
||||
) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_node_connect(output_node, input_node, input_id)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::node::oaknode_node_connect(output_node, input_node, input_id) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_node_disconnect(input_node: OakNodeNode, input_id: *const c_char) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_node_disconnect(input_node, input_id)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::node::oaknode_node_disconnect(input_node, input_id) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oaknode` crate (single-lib unification).
|
||||
pub fn oaknode_node_copy_in_graph(
|
||||
node: OakNodeNode,
|
||||
out_command: *mut crate::handle::CHandle,
|
||||
) -> OakNodeNode {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oaknode_node_copy_in_graph(node, out_command)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oaknode::ffi::node::oaknode_node_copy_in_graph(node, out_command) }
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,133 +0,0 @@
|
||||
// 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/>.
|
||||
|
||||
// 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/>.
|
||||
|
||||
//! oakundo C ABI imports (`include/undo/undocommand.h`). Undo commands are
|
||||
//! created through the C ABI vtable (`oakundo_command_init` with Rust
|
||||
//! closures as `userdata`) — timeline does not subclass the C++
|
||||
//! `UndoCommand`; every command struct in this crate exposes `to_command()`
|
||||
//! that wraps it this way.
|
||||
|
||||
use std::ffi::{c_char, c_int, c_void};
|
||||
|
||||
use crate::handle::CHandle;
|
||||
|
||||
/// `OakUndoCommandVtable` — callback table backing a caller-defined undo
|
||||
/// command (undocommand.h). Any callback may be NULL (a NULL redo/undo makes
|
||||
/// that direction a no-op); `free_fn` runs when the command is destroyed.
|
||||
/// Single-lib unification: aliases the oakundo crate's vtable POD.
|
||||
pub type OakUndoCommandVtable = oakundo::undocommand::OakUndoCommandVtable;
|
||||
|
||||
/// Direct call into the `oakundo` crate (single-lib unification).
|
||||
pub fn oakundo_command_init(vtable: *const OakUndoCommandVtable, userdata: *mut c_void) -> CHandle {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oakundo_command_init(vtable, userdata)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oakundo::ffi::command::oakundo_command_init(vtable, userdata) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oakundo` crate (single-lib unification).
|
||||
pub fn oakundo_command_init_multi() -> CHandle {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oakundo_command_init_multi()
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oakundo::ffi::command::oakundo_command_init_multi() }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oakundo` crate (single-lib unification).
|
||||
pub fn oakundo_command_multi_add_child(multi: CHandle, child: CHandle) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oakundo_command_multi_add_child(multi, child)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oakundo::ffi::command::oakundo_command_multi_add_child(multi, child) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oakundo` crate (single-lib unification).
|
||||
pub fn oakundo_command_redo_now(command: CHandle) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oakundo_command_redo_now(command)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oakundo::ffi::command::oakundo_command_redo_now(command) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oakundo` crate (single-lib unification).
|
||||
pub fn oakundo_command_undo_now(command: CHandle) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oakundo_command_undo_now(command)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oakundo::ffi::command::oakundo_command_undo_now(command) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oakundo` crate (single-lib unification).
|
||||
pub fn oakundo_command_free(command: *mut CHandle) {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oakundo_command_free(command)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oakundo::ffi::command::oakundo_command_free(command) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Direct call into the `oakundo` crate (single-lib unification; the C ABI
|
||||
/// name is `oakundo_undostack_push` — kept as `oakundo_stack_push` here for
|
||||
/// source compatibility).
|
||||
pub fn oakundo_stack_push(stack: CHandle, command: CHandle, text: *const c_char) -> c_int {
|
||||
#[cfg(any(test, feature = "test-stubs"))]
|
||||
{
|
||||
super::teststubs::oakundo_stack_push(stack, command, text)
|
||||
}
|
||||
#[cfg(not(any(test, feature = "test-stubs")))]
|
||||
{
|
||||
unsafe { oakundo::ffi::undostack::oakundo_undostack_push(stack, command, text) }
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,8 @@
|
||||
//!
|
||||
//! Enum discriminants are value-compatible with both `include/timeline/edit.h`
|
||||
//! (`OAKTIMELINE_MOVEMENT_*`) and `include/timeline/displaymode.h`
|
||||
//! (`OAK_TIMELINE_THUMBNAIL_*` / `OAK_TIMELINE_WAVEFORMS_*`); `ffi.rs` maps
|
||||
//! between them mechanically.
|
||||
//! (`OAK_TIMELINE_THUMBNAIL_*` / `OAK_TIMELINE_WAVEFORMS_*`); the deleted C
|
||||
//! ABI export layer used to map between them mechanically.
|
||||
|
||||
use oakcore_rs::Rational;
|
||||
|
||||
@@ -92,13 +92,14 @@ pub enum WaveformMode {
|
||||
}
|
||||
|
||||
/// `Timeline::EditToInfo` (timelinecommon.h): where the nearest block edge
|
||||
/// falls for a pointer edit. Not `Copy`/`Clone`/`Debug` — it owns
|
||||
/// [`crate::handle::CHandle`]s, which are opaque refcounted handles.
|
||||
/// falls for a pointer edit. The node references are the single-lib
|
||||
/// domain handles ([`crate::util::NodeRef`] — `None` where the C ABI
|
||||
/// used a null handle).
|
||||
pub struct EditToInfo {
|
||||
/// Owning track of the nearest block.
|
||||
pub track: crate::handle::CHandle,
|
||||
pub track: Option<crate::util::NodeRef>,
|
||||
/// Nearest edit point time.
|
||||
pub nearest_time: Rational,
|
||||
/// Nearest block.
|
||||
pub nearest_block: crate::handle::CHandle,
|
||||
pub nearest_block: Option<crate::util::NodeRef>,
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -41,8 +41,7 @@ pub struct RefBox<T: ?Sized> {
|
||||
/// The shared ABI value-handle type (single-lib unification, see
|
||||
/// `docs/zh/plans/riir/single-lib.md`): one canonical
|
||||
/// `{ctx, addref, release, abi_version}` type in `oakcore-rs`, re-exported
|
||||
/// here so the crate's `ffi.rs` signatures and handle scaffolding stay
|
||||
/// source-compatible.
|
||||
/// here so the crate's handle scaffolding stays source-compatible.
|
||||
pub use oakcore_rs::handle::CHandle;
|
||||
|
||||
/// Generic `addref` implementation: increments the box's reference count.
|
||||
|
||||
@@ -21,20 +21,20 @@
|
||||
//! architectural mapping (per-header domain modules, no UndoCommand
|
||||
//! inheritance → vtable commands).
|
||||
//!
|
||||
//! ## FFI discipline
|
||||
//! ## Single-lib unification
|
||||
//!
|
||||
//! Identical to the oaknode crate: every export goes through
|
||||
//! [`handle::guard*`], handles are opaque refcounted boxes, and all
|
||||
//! cross-module access goes through the oaknode/oakundo/oakcommon C ABIs
|
||||
//! (`bridge::*`).
|
||||
//! The C ABI export layer (`ffi.rs`) and the oaknode/oakundo/oakcommon
|
||||
//! bridge (`bridge/`) were deleted in the single-lib unification: undo
|
||||
//! commands are now `oakundo::undocommand::UndoCommand` values and every
|
||||
//! node/block/track reference is a [`util::NodeRef`] — an
|
||||
//! `Arc<Mutex<oaknode::project::Project>>` + `oaknode::id::NodeId` pair
|
||||
//! that the commands manipulate through the oaknode Rust domain directly.
|
||||
|
||||
#![deny(unsafe_op_in_unsafe_fn)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
pub mod bridge;
|
||||
pub mod common;
|
||||
pub mod error;
|
||||
pub mod ffi;
|
||||
pub mod handle;
|
||||
pub mod marker;
|
||||
pub mod undocommon;
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
//! order via `TimelineMarkerList::resort`/`resort_at`.
|
||||
|
||||
use oakcore_rs::{Rational, TimeRange};
|
||||
use oakundo::undocommand::UndoCommand;
|
||||
|
||||
use crate::handle::{get, get_mut};
|
||||
use crate::undocommon::{box_command, Command};
|
||||
@@ -232,7 +233,7 @@ fn rational_abs(r: Rational) -> Rational {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Marker undo commands. Each struct exposes prepare()/redo()/undo(); the
|
||||
// FFI layer wraps it through bridge::undo's vtable (to_command()).
|
||||
// undo stack wraps it through undocommon's vtable (to_command()).
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// `MarkerAddCommand` (timelinemarker.h).
|
||||
@@ -302,7 +303,7 @@ impl MarkerAddCommand {
|
||||
}
|
||||
|
||||
/// Wrap as an oakundo vtable command handle.
|
||||
pub fn to_command(self) -> crate::handle::CHandle {
|
||||
pub fn to_command(self) -> UndoCommand {
|
||||
box_command(self)
|
||||
}
|
||||
}
|
||||
@@ -369,7 +370,7 @@ impl MarkerRemoveCommand {
|
||||
}
|
||||
|
||||
/// Wrap as an oakundo vtable command handle.
|
||||
pub fn to_command(self) -> crate::handle::CHandle {
|
||||
pub fn to_command(self) -> UndoCommand {
|
||||
box_command(self)
|
||||
}
|
||||
}
|
||||
@@ -437,7 +438,7 @@ impl MarkerChangeColorCommand {
|
||||
}
|
||||
|
||||
/// Wrap as an oakundo vtable command handle.
|
||||
pub fn to_command(self) -> crate::handle::CHandle {
|
||||
pub fn to_command(self) -> UndoCommand {
|
||||
box_command(self)
|
||||
}
|
||||
}
|
||||
@@ -505,7 +506,7 @@ impl MarkerChangeNameCommand {
|
||||
}
|
||||
|
||||
/// Wrap as an oakundo vtable command handle.
|
||||
pub fn to_command(self) -> crate::handle::CHandle {
|
||||
pub fn to_command(self) -> UndoCommand {
|
||||
box_command(self)
|
||||
}
|
||||
}
|
||||
@@ -576,7 +577,7 @@ impl MarkerChangeTimeCommand {
|
||||
}
|
||||
|
||||
/// Wrap as an oakundo vtable command handle.
|
||||
pub fn to_command(self) -> crate::handle::CHandle {
|
||||
pub fn to_command(self) -> UndoCommand {
|
||||
box_command(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,72 +14,122 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Shared node-removal helpers and the C-command wrapper
|
||||
//! (`src/timeline/src/timelineundocommon.h`). All node-graph access goes
|
||||
//! through the oaknode C ABI (`bridge::node`) and undo through
|
||||
//! `bridge::undo`.
|
||||
//! Shared node-removal helpers and the command wrapper
|
||||
//! (`src/timeline/src/timelineundocommon.h`). Since the single-lib
|
||||
//! unification, commands are `oakundo::undocommand::UndoCommand` values
|
||||
//! (the oakundo C ABI and the oaknode C ABI are both gone); node-graph
|
||||
//! access goes directly through the oaknode Rust domain
|
||||
//! ([`crate::util::NodeRef`] + the project's graph arena).
|
||||
//!
|
||||
//! `CHandleCommandWrapper` in C++ subclasses `olive::UndoCommand` to wrap a
|
||||
//! raw `OakUndoCommand`; the Rust equivalent holds a command `CHandle` and
|
||||
//! forwards `redo`/`undo` to `bridge::undo::oakundo_command_redo_now/undo_now`.
|
||||
//! raw `OakUndoCommand`; the Rust equivalent holds an
|
||||
//! [`oakundo::undocommand::UndoCommand`] and forwards `redo`/`undo` to
|
||||
//! `redo_now`/`undo_now`.
|
||||
|
||||
use std::ffi::c_void;
|
||||
|
||||
use crate::bridge::node::{
|
||||
oaknode_block_as_node, oaknode_command_create_remove_node, oaknode_node_output_connection_count,
|
||||
};
|
||||
use crate::bridge::undo::{
|
||||
oakundo_command_free, oakundo_command_init, oakundo_command_redo_now, oakundo_command_undo_now,
|
||||
};
|
||||
use crate::handle::CHandle;
|
||||
use oaknode::graph::NodeEntry;
|
||||
use oakundo::undocommand::{OakUndoCommandVtable, UndoCommand};
|
||||
|
||||
use crate::util::{block_add_to_graph, block_remove_from_graph, NodeRef};
|
||||
|
||||
/// An empty (all callbacks absent) command — the failure-path result of
|
||||
/// the deleted oaknode remove-command factory (an empty command handle).
|
||||
pub(crate) fn empty_command() -> UndoCommand {
|
||||
UndoCommand::from_vtable(
|
||||
OakUndoCommandVtable {
|
||||
redo: None,
|
||||
undo: None,
|
||||
free_fn: None,
|
||||
},
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
}
|
||||
|
||||
/// `oaknode_command_create_remove_node` — a command that detaches a node
|
||||
/// from the project graph on `redo` (the detached entry is owned by this
|
||||
/// command) and re-inserts it, identity-preserving, on `undo`. The C++
|
||||
/// equivalent runs `node->setParent(&memory_manager_)` / the reverse.
|
||||
struct RemoveNodeCommand {
|
||||
/// The node to remove.
|
||||
node: NodeRef,
|
||||
/// Arena entry detached on `redo`, owned here until `undo`.
|
||||
entry: Option<NodeEntry>,
|
||||
}
|
||||
|
||||
impl RemoveNodeCommand {
|
||||
/// Construct from the node to remove.
|
||||
fn new(node: NodeRef) -> Self {
|
||||
Self { node, entry: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl Command for RemoveNodeCommand {
|
||||
/// `redo`: detach the node from the graph.
|
||||
fn redo(&mut self) {
|
||||
if self.entry.is_none() {
|
||||
self.entry = block_remove_from_graph(&self.node);
|
||||
}
|
||||
}
|
||||
|
||||
/// `undo`: re-insert the node into the graph at its original slot.
|
||||
fn undo(&mut self) {
|
||||
if let Some(entry) = self.entry.take() {
|
||||
block_add_to_graph(&self.node, Some(entry));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `node_can_be_removed(Node)`: true when the node has no output
|
||||
/// connections (timelineundocommon.h).
|
||||
pub fn node_can_be_removed(node: CHandle) -> bool {
|
||||
let mut count = 0;
|
||||
// SAFETY: `count` is a valid out pointer.
|
||||
let _ = unsafe { oaknode_node_output_connection_count(node, &mut count) };
|
||||
count == 0
|
||||
pub fn node_can_be_removed(node: &NodeRef) -> bool {
|
||||
let p = node.project.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
p.graph.output_connections(node.id).is_empty()
|
||||
}
|
||||
|
||||
/// `node_can_be_removed(Block)`: delegates via `oaknode_block_as_node`.
|
||||
pub fn block_can_be_removed(block: CHandle) -> bool {
|
||||
node_can_be_removed(unsafe { oaknode_block_as_node(block) })
|
||||
/// `node_can_be_removed(Block)`: the block is checked through its
|
||||
/// generic-node view.
|
||||
pub fn block_can_be_removed(block: &NodeRef) -> bool {
|
||||
node_can_be_removed(block)
|
||||
}
|
||||
|
||||
/// `create_remove_command(Node)` — `oaknode_command_create_remove_node`.
|
||||
pub fn create_remove_command(node: CHandle) -> CHandle {
|
||||
unsafe { oaknode_command_create_remove_node(node) }
|
||||
pub fn create_remove_command(node: &NodeRef) -> UndoCommand {
|
||||
box_command(RemoveNodeCommand::new(node.clone()))
|
||||
}
|
||||
|
||||
/// `create_remove_command(Block)` — via `oaknode_block_as_node`.
|
||||
pub fn create_block_remove_command(block: CHandle) -> CHandle {
|
||||
create_remove_command(unsafe { oaknode_block_as_node(block) })
|
||||
/// `create_remove_command(Block)`.
|
||||
pub fn create_block_remove_command(block: &NodeRef) -> UndoCommand {
|
||||
create_remove_command(block)
|
||||
}
|
||||
|
||||
/// `create_and_run_remove_command(Node)` — create then `redo_now`.
|
||||
pub fn create_and_run_remove_command(node: CHandle) -> CHandle {
|
||||
let command = create_remove_command(node);
|
||||
// SAFETY: `command` is a valid handle returned by the bridge.
|
||||
let _ = unsafe { oakundo_command_redo_now(command.clone()) };
|
||||
pub fn create_and_run_remove_command(node: &NodeRef) -> UndoCommand {
|
||||
let mut command = create_remove_command(node);
|
||||
command.redo_now();
|
||||
command
|
||||
}
|
||||
|
||||
/// `create_and_run_remove_command(Block)` — via `oaknode_block_as_node`.
|
||||
pub fn create_and_run_block_remove_command(block: CHandle) -> CHandle {
|
||||
create_and_run_remove_command(unsafe { oaknode_block_as_node(block) })
|
||||
/// `create_and_run_remove_command(Block)`.
|
||||
pub fn create_and_run_block_remove_command(block: &NodeRef) -> UndoCommand {
|
||||
create_and_run_remove_command(block)
|
||||
}
|
||||
|
||||
/// `free_command_handle`: release and null a command handle; NULL/empty
|
||||
/// no-op.
|
||||
pub fn free_command_handle(command: *mut CHandle) {
|
||||
// SAFETY: passed through to the bridge, which handles NULL.
|
||||
unsafe { oakundo_command_free(command) };
|
||||
/// `free_command_handle`: release and null a command; NULL no-op. With the
|
||||
/// `UndoCommand` value type, "freeing" is overwriting the pointee with an
|
||||
/// empty command (the old value drops, running its `free_fn`).
|
||||
pub fn free_command_handle(command: *mut UndoCommand) {
|
||||
if command.is_null() {
|
||||
return;
|
||||
}
|
||||
// SAFETY: the caller passes a valid pointer; overwriting drops the old
|
||||
// value and installs the no-op command.
|
||||
unsafe { *command = empty_command() };
|
||||
}
|
||||
|
||||
/// Trait implemented by every timeline undo command. The crate's commands
|
||||
/// are boxed into an oakundo vtable command via [`box_command`]; the bridge's
|
||||
/// `redo_now`/`undo_now` dispatch to these callbacks.
|
||||
/// are boxed into an oakundo vtable command via [`box_command`]; the
|
||||
/// command's `redo_now`/`undo_now` dispatch to these callbacks.
|
||||
pub trait Command {
|
||||
/// Apply the change.
|
||||
fn redo(&mut self);
|
||||
@@ -96,7 +146,7 @@ unsafe extern "C" fn redo_cb<T: Command>(userdata: *mut c_void) {
|
||||
return;
|
||||
}
|
||||
// SAFETY: box_command allocated a `Box<T>`; still alive because the
|
||||
// command handle owns it.
|
||||
// command owns it.
|
||||
(unsafe { &mut *(userdata as *mut T) }).redo();
|
||||
}
|
||||
|
||||
@@ -120,66 +170,56 @@ unsafe extern "C" fn free_cb<T: Command>(userdata: *mut c_void) {
|
||||
if userdata.is_null() {
|
||||
return;
|
||||
}
|
||||
// SAFETY: the handle owns this box; destroying the handle drops it.
|
||||
// SAFETY: the command owns this box; dropping it frees the command.
|
||||
unsafe { drop(Box::from_raw(userdata as *mut T)) };
|
||||
}
|
||||
|
||||
/// Box a command into an oakundo vtable command handle. The handle owns the
|
||||
/// boxed `T`; `redo_now`/`undo_now` dispatch to `T::redo`/`T::undo`, and
|
||||
/// freeing the handle drops `T`.
|
||||
pub(crate) fn box_command<T: Command + 'static>(cmd: T) -> CHandle {
|
||||
/// Box a command into an oakundo vtable command value. The command owns
|
||||
/// the boxed `T`; `redo_now`/`undo_now` dispatch to `T::redo`/`T::undo`,
|
||||
/// and dropping the command drops `T`.
|
||||
pub(crate) fn box_command<T: Command + 'static>(cmd: T) -> UndoCommand {
|
||||
let userdata = Box::into_raw(Box::new(cmd)) as *mut c_void;
|
||||
let vtable = crate::bridge::undo::OakUndoCommandVtable {
|
||||
let vtable = OakUndoCommandVtable {
|
||||
redo: Some(redo_cb::<T>),
|
||||
undo: Some(undo_cb::<T>),
|
||||
free_fn: Some(free_cb::<T>),
|
||||
};
|
||||
// SAFETY: `vtable` and `userdata` remain valid for the command's lifetime
|
||||
// (the bridge copies the vtable and owns `userdata`).
|
||||
unsafe { oakundo_command_init(&vtable, userdata) }
|
||||
// `from_vtable` copies the vtable and takes ownership of `userdata`.
|
||||
UndoCommand::from_vtable(vtable, userdata)
|
||||
}
|
||||
|
||||
/// `CHandleCommandWrapper` — an oakundo vtable command exposed as a
|
||||
/// timeline-level command. `redo`/`undo` forward to `bridge::undo`;
|
||||
/// dropping frees the handle.
|
||||
/// `CHandleCommandWrapper` — an oakundo command exposed as a timeline-level
|
||||
/// command. `redo`/`undo` forward to `redo_now`/`undo_now`; dropping drops
|
||||
/// the wrapped command.
|
||||
pub struct CHandleCommandWrapper {
|
||||
/// Wrapped command handle.
|
||||
command: CHandle,
|
||||
/// Wrapped command; `None` mirrors the old empty (null) command handle.
|
||||
command: Option<UndoCommand>,
|
||||
}
|
||||
|
||||
impl CHandleCommandWrapper {
|
||||
/// Construct over an owned command handle.
|
||||
pub fn new(command: CHandle) -> Self {
|
||||
Self { command }
|
||||
/// Construct over an owned command value.
|
||||
pub fn new(command: UndoCommand) -> Self {
|
||||
Self {
|
||||
command: Some(command),
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the wrapped command is non-empty.
|
||||
/// Whether the wrapper holds a command (the old non-null check).
|
||||
pub fn is_valid(&self) -> bool {
|
||||
!self.command.is_null()
|
||||
self.command.is_some()
|
||||
}
|
||||
|
||||
/// `redo`: forward to `oakundo_command_redo_now`.
|
||||
/// `redo`: forward to `UndoCommand::redo_now`.
|
||||
pub fn redo(&mut self) {
|
||||
if !self.command.is_null() {
|
||||
// SAFETY: `self.command` is a valid handle while it is non-empty.
|
||||
let _ = unsafe { oakundo_command_redo_now(self.command.clone()) };
|
||||
if let Some(c) = self.command.as_mut() {
|
||||
c.redo_now();
|
||||
}
|
||||
}
|
||||
|
||||
/// `undo`: forward to `oakundo_command_undo_now`.
|
||||
/// `undo`: forward to `UndoCommand::undo_now`.
|
||||
pub fn undo(&mut self) {
|
||||
if !self.command.is_null() {
|
||||
// SAFETY: `self.command` is a valid handle while it is non-empty.
|
||||
let _ = unsafe { oakundo_command_undo_now(self.command.clone()) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CHandleCommandWrapper {
|
||||
fn drop(&mut self) {
|
||||
if !self.command.is_null() {
|
||||
// SAFETY: `self.command` is a valid owned handle.
|
||||
unsafe { oakundo_command_free(&mut self.command) };
|
||||
if let Some(c) = self.command.as_mut() {
|
||||
c.undo_now();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,7 +227,7 @@ impl Drop for CHandleCommandWrapper {
|
||||
/// `MultiUndoCommand` — a command that runs several child commands in order
|
||||
/// on `redo` and in reverse on `undo` (timelineundocommon.h
|
||||
/// `MultiUndoCommand`). Children are boxed `Command`s; the whole group wraps
|
||||
/// into a single oakundo vtable handle via [`box_command`].
|
||||
/// into a single oakundo vtable command via [`box_command`].
|
||||
pub struct MultiUndoCommand {
|
||||
/// Child commands, run in order on `redo`.
|
||||
commands: Vec<Box<dyn Command>>,
|
||||
@@ -211,8 +251,8 @@ impl MultiUndoCommand {
|
||||
self.commands.is_empty()
|
||||
}
|
||||
|
||||
/// Wrap as an oakundo vtable command handle.
|
||||
pub fn to_command(self) -> CHandle {
|
||||
/// Wrap as an oakundo vtable command value.
|
||||
pub fn to_command(self) -> UndoCommand {
|
||||
box_command(self)
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,28 +18,25 @@
|
||||
//! that compensate the adjacent block (`BlockTrimCommand`), slides
|
||||
//! (`TrackSlideCommand`) and destructive placement (`TrackPlaceBlockCommand`).
|
||||
//!
|
||||
//! Graph mutation goes through the oaknode C ABI (`bridge::node`); command
|
||||
//! wrapping through `bridge::undo`.
|
||||
//! Graph mutation routes directly through the oaknode Rust domain (the
|
||||
//! oaknode C ABI was deleted in the single-lib unification); command
|
||||
//! wrapping through `oakundo::undocommand::UndoCommand` values.
|
||||
|
||||
use oakcore_rs::{Rational, TimeRange};
|
||||
use oaknode::graph::NodeEntry;
|
||||
use oakundo::undocommand::UndoCommand;
|
||||
|
||||
use crate::bridge::node::{
|
||||
oaknode_block_gap_create, oaknode_block_get_kind, oaknode_track_insert_block_after,
|
||||
oaknode_track_ripple_remove_block, oaknode_tracklist_get_track_at,
|
||||
oaknode_tracklist_get_track_count,
|
||||
};
|
||||
use crate::bridge::undo::{oakundo_command_redo_now, oakundo_command_undo_now};
|
||||
use crate::common::MovementMode;
|
||||
use crate::handle::CHandle;
|
||||
use crate::undocommon::{
|
||||
block_can_be_removed, box_command, create_and_run_block_remove_command,
|
||||
create_block_remove_command, Command, MultiUndoCommand,
|
||||
};
|
||||
use crate::util::{
|
||||
block_add_to_graph, block_in, block_length, block_next, block_previous,
|
||||
block_remove_from_graph, block_set_in, block_set_length_and_media_in,
|
||||
block_set_length_and_media_out, block_track, track_append_block, track_insert_block_before,
|
||||
track_length,
|
||||
block_add_to_graph, block_gap_create, block_in, block_kind, block_length, block_next,
|
||||
block_out, block_previous, block_remove_from_graph, block_set_in,
|
||||
block_set_length_and_media_in, block_set_length_and_media_out, block_track,
|
||||
track_append_block, track_insert_block_after, track_insert_block_before, track_length,
|
||||
track_ripple_remove_block, tracklist_track_at, tracklist_track_count, BlockKind, NodeRef,
|
||||
};
|
||||
|
||||
use super::undogeneral::{TimelineAddTrackCommand, TrackReplaceBlockWithGapCommand};
|
||||
@@ -53,9 +50,9 @@ use super::undoripple::TrackRippleRemoveAreaCommand;
|
||||
/// always trim into the adjacent block instead.
|
||||
pub struct BlockTrimCommand {
|
||||
/// Owning track (`track_`).
|
||||
track: CHandle,
|
||||
track: NodeRef,
|
||||
/// Block to trim (`block_`).
|
||||
block: CHandle,
|
||||
block: NodeRef,
|
||||
/// New length (`new_length_`).
|
||||
new_length: Rational,
|
||||
/// Trim direction (`mode_`).
|
||||
@@ -69,7 +66,7 @@ pub struct BlockTrimCommand {
|
||||
/// Block length before the trim (`old_length_`).
|
||||
old_length_: Rational,
|
||||
/// The block to compensate (`adjacent_`).
|
||||
adjacent_: CHandle,
|
||||
adjacent_: Option<NodeRef>,
|
||||
/// Whether the adjacent block is required for this trim (`needs_adjacent_`).
|
||||
needs_adjacent_: bool,
|
||||
/// Whether `adjacent_` was created by this command (`we_created_adjacent_`).
|
||||
@@ -77,26 +74,23 @@ pub struct BlockTrimCommand {
|
||||
/// Whether the adjacent block is removed by this trim (`we_removed_adjacent_`).
|
||||
we_removed_adjacent_: bool,
|
||||
/// Graph-removal command for a removed adjacent (`deleted_adjacent_command_`).
|
||||
///
|
||||
/// Owns its handle like the C++ `OakUndoCommand`; `CHandle` is refcounted
|
||||
/// and drops without a destructor, so releasing it is implicit — no explicit
|
||||
/// `free_command_handle` is needed here.
|
||||
deleted_adjacent_command_: CHandle,
|
||||
deleted_adjacent_command_: Option<UndoCommand>,
|
||||
/// Always trim into the adjacent block instead of creating a gap
|
||||
/// (`trim_is_a_roll_edit_`).
|
||||
trim_is_a_roll_edit_: bool,
|
||||
/// Remove a zero-length adjacent block from the whole graph (default true)
|
||||
/// rather than only from the track (`remove_block_from_graph_`).
|
||||
remove_block_from_graph_: bool,
|
||||
/// Whether `adjacent_` is a created gap still detached from the graph
|
||||
/// (`adjacent_orphaned_`); a detached handle is owned by this command until
|
||||
/// it is added to the graph.
|
||||
adjacent_orphaned_: bool,
|
||||
/// Arena entry of a created adjacent gap while it is detached from the
|
||||
/// graph (between `undo` and the next `redo`).
|
||||
adjacent_entry_: Option<NodeEntry>,
|
||||
}
|
||||
|
||||
impl BlockTrimCommand {
|
||||
/// Construct from track + block + new length + movement mode.
|
||||
pub fn new(track: CHandle, block: CHandle, new_length: Rational, mode: MovementMode) -> Self {
|
||||
///
|
||||
/// New signature (single-lib): `pub fn new(track: NodeRef, block: NodeRef, new_length: Rational, mode: MovementMode) -> BlockTrimCommand`
|
||||
pub fn new(track: NodeRef, block: NodeRef, new_length: Rational, mode: MovementMode) -> Self {
|
||||
Self {
|
||||
track,
|
||||
block,
|
||||
@@ -105,14 +99,14 @@ impl BlockTrimCommand {
|
||||
doing_nothing_: false,
|
||||
trim_diff_: Rational::new(0, 1),
|
||||
old_length_: Rational::new(0, 1),
|
||||
adjacent_: CHandle::null(),
|
||||
adjacent_: None,
|
||||
needs_adjacent_: false,
|
||||
we_created_adjacent_: false,
|
||||
we_removed_adjacent_: false,
|
||||
deleted_adjacent_command_: CHandle::null(),
|
||||
deleted_adjacent_command_: None,
|
||||
trim_is_a_roll_edit_: false,
|
||||
remove_block_from_graph_: true,
|
||||
adjacent_orphaned_: false,
|
||||
adjacent_entry_: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +126,7 @@ impl BlockTrimCommand {
|
||||
/// `prepare`: compute the trim delta and whether an adjacent block is needed.
|
||||
pub fn prepare(&mut self) {
|
||||
// Store old length
|
||||
self.old_length_ = block_length(self.block.clone());
|
||||
self.old_length_ = block_length(&self.block);
|
||||
|
||||
// If the length isn't changing, set a flag to do nothing
|
||||
if self.old_length_ == self.new_length {
|
||||
@@ -144,40 +138,44 @@ impl BlockTrimCommand {
|
||||
// Positive when trimming shorter, negative when trimming longer
|
||||
self.trim_diff_ = self.old_length_ - self.new_length;
|
||||
|
||||
// Retrieve our adjacent block (or an empty handle if none)
|
||||
// Retrieve our adjacent block (or None if there is none)
|
||||
self.adjacent_ = if self.mode == MovementMode::TrimIn {
|
||||
block_previous(self.block.clone())
|
||||
block_previous(&self.block)
|
||||
} else {
|
||||
block_next(self.block.clone())
|
||||
block_next(&self.block)
|
||||
};
|
||||
|
||||
// Ignore when trimming the out with no adjacent, because the user must
|
||||
// have trimmed the end of the last block in the track
|
||||
self.needs_adjacent_ = self.mode == MovementMode::TrimIn || !self.adjacent_.is_null();
|
||||
self.needs_adjacent_ = self.mode == MovementMode::TrimIn || self.adjacent_.is_some();
|
||||
|
||||
if self.needs_adjacent_ {
|
||||
// If we're trimming shorter, we need an adjacent, so check if we have a
|
||||
// viable one
|
||||
let mut adjacent_kind = 0; // OAKNODE_BLOCK_OTHER
|
||||
if !self.adjacent_.is_null() {
|
||||
// SAFETY: `adjacent_kind` is a valid out pointer.
|
||||
let _ =
|
||||
unsafe { oaknode_block_get_kind(self.adjacent_.clone(), &mut adjacent_kind) };
|
||||
}
|
||||
let adjacent_kind = self.adjacent_.as_ref().map(block_kind).unwrap_or(BlockKind::Other);
|
||||
self.we_created_adjacent_ = self.trim_diff_ > Rational::new(0, 1)
|
||||
&& (self.adjacent_.is_null() || (adjacent_kind != 2 && !self.trim_is_a_roll_edit_));
|
||||
&& (self.adjacent_.is_none()
|
||||
|| (adjacent_kind != BlockKind::Gap && !self.trim_is_a_roll_edit_));
|
||||
|
||||
if self.we_created_adjacent_ {
|
||||
// We shortened but don't have a viable adjacent to lengthen, so create
|
||||
// one
|
||||
// SAFETY: `oaknode_block_gap_create` returns a fresh owned handle.
|
||||
self.adjacent_ = unsafe { oaknode_block_gap_create() };
|
||||
block_set_length_and_media_out(self.adjacent_.clone(), self.trim_diff_);
|
||||
self.adjacent_orphaned_ = true;
|
||||
} else {
|
||||
// one filling exactly the space the trim freed: for a trim-in it
|
||||
// spans [in - diff, in), for a trim-out [out, out + diff). The
|
||||
// module stores positions on the block, so both the in point and
|
||||
// the length are written explicitly.
|
||||
self.adjacent_ = Some(block_gap_create(&self.track.project));
|
||||
if let Some(gap) = &self.adjacent_ {
|
||||
if self.mode == MovementMode::TrimIn {
|
||||
block_set_in(gap, block_in(&self.block) - self.trim_diff_);
|
||||
} else {
|
||||
block_set_in(gap, block_out(&self.block));
|
||||
}
|
||||
block_set_length_and_media_in(gap, self.trim_diff_);
|
||||
}
|
||||
} else if let Some(adjacent) = &self.adjacent_ {
|
||||
// Determine if we're removing the adjacent
|
||||
self.we_removed_adjacent_ =
|
||||
(block_length(self.adjacent_.clone()) + self.trim_diff_).is_null();
|
||||
(block_length(adjacent) + self.trim_diff_).is_null();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -189,57 +187,43 @@ impl BlockTrimCommand {
|
||||
}
|
||||
|
||||
if self.mode == MovementMode::TrimIn {
|
||||
block_set_length_and_media_in(self.block.clone(), self.new_length);
|
||||
block_set_length_and_media_in(&self.block, self.new_length);
|
||||
} else {
|
||||
block_set_length_and_media_out(self.block.clone(), self.new_length);
|
||||
block_set_length_and_media_out(&self.block, self.new_length);
|
||||
}
|
||||
|
||||
if self.needs_adjacent_ {
|
||||
if self.we_created_adjacent_ {
|
||||
// Add the adjacent and insert it
|
||||
block_add_to_graph(self.adjacent_.clone(), self.track.clone());
|
||||
if self.mode == MovementMode::TrimIn {
|
||||
track_insert_block_before(
|
||||
self.track.clone(),
|
||||
self.adjacent_.clone(),
|
||||
self.block.clone(),
|
||||
);
|
||||
} else {
|
||||
// SAFETY: `track`/`adjacent_`/`block` are valid handles.
|
||||
let _ = unsafe {
|
||||
oaknode_track_insert_block_after(
|
||||
self.track.clone(),
|
||||
self.adjacent_.clone(),
|
||||
self.block.clone(),
|
||||
)
|
||||
};
|
||||
}
|
||||
self.adjacent_orphaned_ = false;
|
||||
} else if self.we_removed_adjacent_ {
|
||||
// SAFETY: valid handles.
|
||||
let _ = unsafe {
|
||||
oaknode_track_ripple_remove_block(self.track.clone(), self.adjacent_.clone())
|
||||
};
|
||||
|
||||
// It no longer inputs/outputs anything, remove it
|
||||
if self.remove_block_from_graph_ && block_can_be_removed(self.adjacent_.clone()) {
|
||||
if self.deleted_adjacent_command_.is_null() {
|
||||
self.deleted_adjacent_command_ =
|
||||
create_and_run_block_remove_command(self.adjacent_.clone());
|
||||
if let Some(gap) = &self.adjacent_ {
|
||||
block_add_to_graph(gap, self.adjacent_entry_.take());
|
||||
if self.mode == MovementMode::TrimIn {
|
||||
track_insert_block_before(&self.track, gap, &self.block);
|
||||
} else {
|
||||
// SAFETY: `deleted_adjacent_command_` is a valid command handle.
|
||||
let _ = unsafe {
|
||||
oakundo_command_redo_now(self.deleted_adjacent_command_.clone())
|
||||
};
|
||||
track_insert_block_after(&self.track, gap, Some(&self.block));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let adjacent_length = block_length(self.adjacent_.clone()) + self.trim_diff_;
|
||||
} else if self.we_removed_adjacent_ {
|
||||
if let Some(adjacent) = &self.adjacent_ {
|
||||
track_ripple_remove_block(&self.track, adjacent);
|
||||
|
||||
// It no longer inputs/outputs anything, remove it
|
||||
if self.remove_block_from_graph_ && block_can_be_removed(adjacent) {
|
||||
if self.deleted_adjacent_command_.is_none() {
|
||||
self.deleted_adjacent_command_ =
|
||||
Some(create_and_run_block_remove_command(adjacent));
|
||||
} else if let Some(c) = self.deleted_adjacent_command_.as_mut() {
|
||||
c.redo_now();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some(adjacent) = &self.adjacent_ {
|
||||
let adjacent_length = block_length(adjacent) + self.trim_diff_;
|
||||
|
||||
if self.mode == MovementMode::TrimIn {
|
||||
block_set_length_and_media_out(self.adjacent_.clone(), adjacent_length);
|
||||
block_set_length_and_media_out(adjacent, adjacent_length);
|
||||
} else {
|
||||
block_set_length_and_media_in(self.adjacent_.clone(), adjacent_length);
|
||||
block_set_length_and_media_in(adjacent, adjacent_length);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -256,59 +240,45 @@ impl BlockTrimCommand {
|
||||
if self.needs_adjacent_ {
|
||||
if self.we_created_adjacent_ {
|
||||
// The adjacent is ours, just delete it
|
||||
// SAFETY: valid handles.
|
||||
let _ = unsafe {
|
||||
oaknode_track_ripple_remove_block(self.track.clone(), self.adjacent_.clone())
|
||||
};
|
||||
block_remove_from_graph(self.adjacent_.clone(), self.track.clone());
|
||||
self.adjacent_orphaned_ = true;
|
||||
} else {
|
||||
if let Some(gap) = &self.adjacent_ {
|
||||
track_ripple_remove_block(&self.track, gap);
|
||||
if self.adjacent_entry_.is_none() {
|
||||
self.adjacent_entry_ = block_remove_from_graph(gap);
|
||||
}
|
||||
}
|
||||
} else if let Some(adjacent) = &self.adjacent_ {
|
||||
if self.we_removed_adjacent_ {
|
||||
if !self.deleted_adjacent_command_.is_null() {
|
||||
if let Some(c) = self.deleted_adjacent_command_.as_mut() {
|
||||
// We deleted the adjacent, restore it now
|
||||
// SAFETY: `deleted_adjacent_command_` is a valid command handle.
|
||||
let _ = unsafe {
|
||||
oakundo_command_undo_now(self.deleted_adjacent_command_.clone())
|
||||
};
|
||||
c.undo_now();
|
||||
}
|
||||
|
||||
if self.mode == MovementMode::TrimIn {
|
||||
track_insert_block_before(
|
||||
self.track.clone(),
|
||||
self.adjacent_.clone(),
|
||||
self.block.clone(),
|
||||
);
|
||||
track_insert_block_before(&self.track, adjacent, &self.block);
|
||||
} else {
|
||||
// SAFETY: valid handles.
|
||||
let _ = unsafe {
|
||||
oaknode_track_insert_block_after(
|
||||
self.track.clone(),
|
||||
self.adjacent_.clone(),
|
||||
self.block.clone(),
|
||||
)
|
||||
};
|
||||
track_insert_block_after(&self.track, adjacent, Some(&self.block));
|
||||
}
|
||||
} else {
|
||||
let adjacent_length = block_length(self.adjacent_.clone()) - self.trim_diff_;
|
||||
let adjacent_length = block_length(adjacent) - self.trim_diff_;
|
||||
|
||||
if self.mode == MovementMode::TrimIn {
|
||||
block_set_length_and_media_out(self.adjacent_.clone(), adjacent_length);
|
||||
block_set_length_and_media_out(adjacent, adjacent_length);
|
||||
} else {
|
||||
block_set_length_and_media_in(self.adjacent_.clone(), adjacent_length);
|
||||
block_set_length_and_media_in(adjacent, adjacent_length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.mode == MovementMode::TrimIn {
|
||||
block_set_length_and_media_in(self.block.clone(), self.old_length_);
|
||||
block_set_length_and_media_in(&self.block, self.old_length_);
|
||||
} else {
|
||||
block_set_length_and_media_out(self.block.clone(), self.old_length_);
|
||||
block_set_length_and_media_out(&self.block, self.old_length_);
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrap as an oakundo vtable command handle.
|
||||
pub fn to_command(self) -> CHandle {
|
||||
/// Wrap as an oakundo vtable command value.
|
||||
pub fn to_command(self) -> UndoCommand {
|
||||
box_command(self)
|
||||
}
|
||||
}
|
||||
@@ -330,9 +300,9 @@ impl Command for BlockTrimCommand {
|
||||
/// (timelineundopointer.h).
|
||||
pub struct TrackSlideCommand {
|
||||
/// Owning track (`track_`).
|
||||
track: CHandle,
|
||||
track: NodeRef,
|
||||
/// Blocks to slide (`blocks_`; non-empty for a valid command).
|
||||
blocks: Vec<CHandle>,
|
||||
blocks: Vec<NodeRef>,
|
||||
/// Signed movement (`movement_`).
|
||||
movement: Rational,
|
||||
/// Whether `prepare` created the in adjacent (`we_created_in_adjacent_`).
|
||||
@@ -340,35 +310,32 @@ pub struct TrackSlideCommand {
|
||||
/// Whether the slide removes the in adjacent (`we_removed_in_adjacent_`).
|
||||
we_removed_in_adjacent_: bool,
|
||||
/// Block adjacent on the in side (`in_adjacent_`).
|
||||
in_adjacent: CHandle,
|
||||
/// Graph-removal command for a removed in adjacent (`in_adjacent_remove_command_`).
|
||||
///
|
||||
/// Owns its handle like the C++ `OakUndoCommand`; `CHandle` is refcounted
|
||||
/// and drops without a destructor, so releasing it is implicit.
|
||||
in_adjacent_remove_command_: CHandle,
|
||||
/// Whether `in_adjacent_` is a created gap still detached from the graph
|
||||
/// (`in_adjacent_orphaned_`); a detached handle is owned by this command.
|
||||
in_adjacent_orphaned_: bool,
|
||||
in_adjacent: Option<NodeRef>,
|
||||
/// Graph-removal command for a removed in adjacent.
|
||||
in_adjacent_remove_command_: Option<UndoCommand>,
|
||||
/// Arena entry of a created in adjacent gap while detached from the graph.
|
||||
in_adjacent_entry_: Option<NodeEntry>,
|
||||
/// Whether `prepare` created the out adjacent (`we_created_out_adjacent_`).
|
||||
we_created_out_adjacent_: bool,
|
||||
/// Whether the slide removes the out adjacent (`we_removed_out_adjacent_`).
|
||||
we_removed_out_adjacent_: bool,
|
||||
/// Block adjacent on the out side (`out_adjacent_`).
|
||||
out_adjacent: CHandle,
|
||||
/// Graph-removal command for a removed out adjacent (`out_adjacent_remove_command_`).
|
||||
out_adjacent_remove_command_: CHandle,
|
||||
/// Whether `out_adjacent_` is a created gap still detached from the graph
|
||||
/// (`out_adjacent_orphaned_`); a detached handle is owned by this command.
|
||||
out_adjacent_orphaned_: bool,
|
||||
out_adjacent: Option<NodeRef>,
|
||||
/// Graph-removal command for a removed out adjacent.
|
||||
out_adjacent_remove_command_: Option<UndoCommand>,
|
||||
/// Arena entry of a created out adjacent gap while detached from the graph.
|
||||
out_adjacent_entry_: Option<NodeEntry>,
|
||||
}
|
||||
|
||||
impl TrackSlideCommand {
|
||||
/// Construct from track + moving blocks + in/out adjacent blocks + movement.
|
||||
///
|
||||
/// New signature (single-lib): `pub fn new(track: NodeRef, blocks: Vec<NodeRef>, in_adjacent: Option<NodeRef>, out_adjacent: Option<NodeRef>, movement: Rational) -> TrackSlideCommand`
|
||||
pub fn new(
|
||||
track: CHandle,
|
||||
blocks: Vec<CHandle>,
|
||||
in_adjacent: CHandle,
|
||||
out_adjacent: CHandle,
|
||||
track: NodeRef,
|
||||
blocks: Vec<NodeRef>,
|
||||
in_adjacent: Option<NodeRef>,
|
||||
out_adjacent: Option<NodeRef>,
|
||||
movement: Rational,
|
||||
) -> Self {
|
||||
Self {
|
||||
@@ -378,38 +345,44 @@ impl TrackSlideCommand {
|
||||
we_created_in_adjacent_: false,
|
||||
we_removed_in_adjacent_: false,
|
||||
in_adjacent,
|
||||
in_adjacent_remove_command_: CHandle::null(),
|
||||
in_adjacent_orphaned_: false,
|
||||
in_adjacent_remove_command_: None,
|
||||
in_adjacent_entry_: None,
|
||||
we_created_out_adjacent_: false,
|
||||
we_removed_out_adjacent_: false,
|
||||
out_adjacent,
|
||||
out_adjacent_remove_command_: CHandle::null(),
|
||||
out_adjacent_orphaned_: false,
|
||||
out_adjacent_remove_command_: None,
|
||||
out_adjacent_entry_: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// `prepare`: capture adjacent state.
|
||||
pub fn prepare(&mut self) {
|
||||
if self.in_adjacent.is_null() {
|
||||
// SAFETY: `oaknode_block_gap_create` returns a fresh owned handle.
|
||||
self.in_adjacent = unsafe { oaknode_block_gap_create() };
|
||||
block_set_length_and_media_out(self.in_adjacent.clone(), self.movement);
|
||||
self.in_adjacent_orphaned_ = true;
|
||||
if self.in_adjacent.is_none() {
|
||||
self.in_adjacent = Some(block_gap_create(&self.track.project));
|
||||
if let Some(gap) = &self.in_adjacent {
|
||||
// A created in adjacent only makes sense when sliding left
|
||||
// (movement < 0): the gap fills [in0 + movement, in0) ahead
|
||||
// of the first block. Positions are stored on the block in
|
||||
// the module model, so both ends are written explicitly.
|
||||
block_set_in(gap, block_in(&self.blocks[0]) + self.movement);
|
||||
block_set_length_and_media_in(gap, Rational::new(0, 1) - self.movement);
|
||||
}
|
||||
self.we_created_in_adjacent_ = true;
|
||||
} else {
|
||||
self.we_created_in_adjacent_ = false;
|
||||
}
|
||||
|
||||
if self.out_adjacent.is_null()
|
||||
&& !block_next(self.blocks[self.blocks.len() - 1].clone()).is_null()
|
||||
if self.out_adjacent.is_none()
|
||||
&& block_next(self.blocks.last().expect("non-empty blocks")).is_some()
|
||||
{
|
||||
// SAFETY: `oaknode_block_gap_create` returns a fresh owned handle.
|
||||
self.out_adjacent = unsafe { oaknode_block_gap_create() };
|
||||
block_set_length_and_media_out(
|
||||
self.out_adjacent.clone(),
|
||||
Rational::new(0, 1) - self.movement,
|
||||
);
|
||||
self.out_adjacent_orphaned_ = true;
|
||||
self.out_adjacent = Some(block_gap_create(&self.track.project));
|
||||
if let Some(gap) = &self.out_adjacent {
|
||||
// A created out adjacent only makes sense when sliding right
|
||||
// (movement > 0): the gap fills [last_out, last_out + movement)
|
||||
// after the last block.
|
||||
block_set_in(gap, block_out(self.blocks.last().expect("non-empty blocks")));
|
||||
block_set_length_and_media_in(gap, self.movement);
|
||||
}
|
||||
self.we_created_out_adjacent_ = true;
|
||||
} else {
|
||||
self.we_created_out_adjacent_ = false;
|
||||
@@ -421,80 +394,68 @@ impl TrackSlideCommand {
|
||||
// We will always have an in adjacent if there was a valid slide
|
||||
if self.we_created_in_adjacent_ {
|
||||
// We created the in adjacent, so all we have to do is insert it
|
||||
block_add_to_graph(self.in_adjacent.clone(), self.track.clone());
|
||||
// `blocks` is non-empty when the command was constructed validly
|
||||
track_insert_block_before(
|
||||
self.track.clone(),
|
||||
self.in_adjacent.clone(),
|
||||
self.blocks[0].clone(),
|
||||
);
|
||||
self.in_adjacent_orphaned_ = false;
|
||||
} else if Rational::new(0, 1) - self.movement == block_length(self.in_adjacent.clone()) {
|
||||
// Movement will remove the in adjacent
|
||||
// SAFETY: valid handles.
|
||||
let _ = unsafe {
|
||||
oaknode_track_ripple_remove_block(self.track.clone(), self.in_adjacent.clone())
|
||||
};
|
||||
if let Some(gap) = &self.in_adjacent {
|
||||
block_add_to_graph(gap, self.in_adjacent_entry_.take());
|
||||
// `blocks` is non-empty when the command was constructed validly
|
||||
track_insert_block_before(&self.track, gap, &self.blocks[0]);
|
||||
}
|
||||
} else if let Some(adjacent) = &self.in_adjacent {
|
||||
if Rational::new(0, 1) - self.movement == block_length(adjacent) {
|
||||
// Movement will remove the in adjacent
|
||||
track_ripple_remove_block(&self.track, adjacent);
|
||||
|
||||
if block_can_be_removed(self.in_adjacent.clone()) {
|
||||
if self.in_adjacent_remove_command_.is_null() {
|
||||
self.in_adjacent_remove_command_ =
|
||||
create_block_remove_command(self.in_adjacent.clone());
|
||||
if block_can_be_removed(adjacent) {
|
||||
if self.in_adjacent_remove_command_.is_none() {
|
||||
self.in_adjacent_remove_command_ =
|
||||
Some(create_block_remove_command(adjacent));
|
||||
}
|
||||
|
||||
if let Some(c) = self.in_adjacent_remove_command_.as_mut() {
|
||||
c.redo_now();
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: `in_adjacent_remove_command_` is a valid command handle.
|
||||
let _ =
|
||||
unsafe { oakundo_command_redo_now(self.in_adjacent_remove_command_.clone()) };
|
||||
self.we_removed_in_adjacent_ = true;
|
||||
} else {
|
||||
// Simply resize the adjacent
|
||||
block_set_length_and_media_out(
|
||||
adjacent,
|
||||
block_length(adjacent) + self.movement,
|
||||
);
|
||||
}
|
||||
|
||||
self.we_removed_in_adjacent_ = true;
|
||||
} else {
|
||||
// Simply resize the adjacent
|
||||
block_set_length_and_media_out(
|
||||
self.in_adjacent.clone(),
|
||||
block_length(self.in_adjacent.clone()) + self.movement,
|
||||
);
|
||||
}
|
||||
|
||||
// We may not have an out adjacent if the slide was at the end of the track
|
||||
if !self.out_adjacent.is_null() {
|
||||
if let Some(adjacent) = &self.out_adjacent {
|
||||
if self.we_created_out_adjacent_ {
|
||||
// We created the out adjacent, so we just have to insert it
|
||||
block_add_to_graph(self.out_adjacent.clone(), self.track.clone());
|
||||
// SAFETY: valid handles.
|
||||
let _ = unsafe {
|
||||
oaknode_track_insert_block_after(
|
||||
self.track.clone(),
|
||||
self.out_adjacent.clone(),
|
||||
self.blocks[self.blocks.len() - 1].clone(),
|
||||
)
|
||||
};
|
||||
self.out_adjacent_orphaned_ = false;
|
||||
} else if self.movement == block_length(self.out_adjacent.clone()) {
|
||||
block_add_to_graph(adjacent, self.out_adjacent_entry_.take());
|
||||
track_insert_block_after(
|
||||
&self.track,
|
||||
adjacent,
|
||||
Some(self.blocks.last().expect("non-empty blocks")),
|
||||
);
|
||||
} else if self.movement == block_length(adjacent) {
|
||||
// Movement will remove the out adjacent
|
||||
// SAFETY: valid handles.
|
||||
let _ = unsafe {
|
||||
oaknode_track_ripple_remove_block(self.track.clone(), self.out_adjacent.clone())
|
||||
};
|
||||
track_ripple_remove_block(&self.track, adjacent);
|
||||
|
||||
if block_can_be_removed(self.out_adjacent.clone()) {
|
||||
if self.out_adjacent_remove_command_.is_null() {
|
||||
if block_can_be_removed(adjacent) {
|
||||
if self.out_adjacent_remove_command_.is_none() {
|
||||
self.out_adjacent_remove_command_ =
|
||||
create_block_remove_command(self.out_adjacent.clone());
|
||||
Some(create_block_remove_command(adjacent));
|
||||
}
|
||||
|
||||
// SAFETY: `out_adjacent_remove_command_` is a valid command handle.
|
||||
let _ = unsafe {
|
||||
oakundo_command_redo_now(self.out_adjacent_remove_command_.clone())
|
||||
};
|
||||
if let Some(c) = self.out_adjacent_remove_command_.as_mut() {
|
||||
c.redo_now();
|
||||
}
|
||||
}
|
||||
|
||||
self.we_removed_out_adjacent_ = true;
|
||||
} else {
|
||||
// Simply resize the adjacent
|
||||
block_set_length_and_media_in(
|
||||
self.out_adjacent.clone(),
|
||||
block_length(self.out_adjacent.clone()) - self.movement,
|
||||
adjacent,
|
||||
block_length(adjacent) - self.movement,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -504,72 +465,60 @@ impl TrackSlideCommand {
|
||||
pub fn undo(&mut self) {
|
||||
if self.we_created_in_adjacent_ {
|
||||
// We created this, so we can remove it now
|
||||
// SAFETY: valid handles.
|
||||
let _ = unsafe {
|
||||
oaknode_track_ripple_remove_block(self.track.clone(), self.in_adjacent.clone())
|
||||
};
|
||||
block_remove_from_graph(self.in_adjacent.clone(), self.track.clone());
|
||||
self.in_adjacent_orphaned_ = true;
|
||||
} else if self.we_removed_in_adjacent_ {
|
||||
if !self.in_adjacent_remove_command_.is_null() {
|
||||
// We removed this, so we can restore it now
|
||||
// SAFETY: `in_adjacent_remove_command_` is a valid command handle.
|
||||
let _ =
|
||||
unsafe { oakundo_command_undo_now(self.in_adjacent_remove_command_.clone()) };
|
||||
if let Some(gap) = &self.in_adjacent {
|
||||
track_ripple_remove_block(&self.track, gap);
|
||||
if self.in_adjacent_entry_.is_none() {
|
||||
self.in_adjacent_entry_ = block_remove_from_graph(gap);
|
||||
}
|
||||
}
|
||||
|
||||
// `blocks` is non-empty when the command was constructed validly
|
||||
track_insert_block_before(
|
||||
self.track.clone(),
|
||||
self.in_adjacent.clone(),
|
||||
self.blocks[0].clone(),
|
||||
);
|
||||
} else {
|
||||
// Simply resize the adjacent
|
||||
block_set_length_and_media_out(
|
||||
self.in_adjacent.clone(),
|
||||
block_length(self.in_adjacent.clone()) - self.movement,
|
||||
);
|
||||
}
|
||||
|
||||
if !self.out_adjacent.is_null() {
|
||||
if self.we_created_out_adjacent_ {
|
||||
// We created this, so we can remove it now
|
||||
// SAFETY: valid handles.
|
||||
let _ = unsafe {
|
||||
oaknode_track_ripple_remove_block(self.track.clone(), self.out_adjacent.clone())
|
||||
};
|
||||
block_remove_from_graph(self.out_adjacent.clone(), self.track.clone());
|
||||
self.out_adjacent_orphaned_ = true;
|
||||
} else if self.we_removed_out_adjacent_ {
|
||||
if !self.out_adjacent_remove_command_.is_null() {
|
||||
} else if let Some(adjacent) = &self.in_adjacent {
|
||||
if self.we_removed_in_adjacent_ {
|
||||
if let Some(c) = self.in_adjacent_remove_command_.as_mut() {
|
||||
// We removed this, so we can restore it now
|
||||
// SAFETY: `out_adjacent_remove_command_` is a valid command handle.
|
||||
let _ = unsafe {
|
||||
oakundo_command_undo_now(self.out_adjacent_remove_command_.clone())
|
||||
};
|
||||
c.undo_now();
|
||||
}
|
||||
|
||||
// SAFETY: valid handles.
|
||||
let _ = unsafe {
|
||||
oaknode_track_insert_block_after(
|
||||
self.track.clone(),
|
||||
self.out_adjacent.clone(),
|
||||
self.blocks[self.blocks.len() - 1].clone(),
|
||||
)
|
||||
};
|
||||
// `blocks` is non-empty when the command was constructed validly
|
||||
track_insert_block_before(&self.track, adjacent, &self.blocks[0]);
|
||||
} else {
|
||||
// Simply resize the adjacent
|
||||
block_set_length_and_media_out(
|
||||
adjacent,
|
||||
block_length(adjacent) - self.movement,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(adjacent) = &self.out_adjacent {
|
||||
if self.we_created_out_adjacent_ {
|
||||
// We created this, so we can remove it now
|
||||
track_ripple_remove_block(&self.track, adjacent);
|
||||
if self.out_adjacent_entry_.is_none() {
|
||||
self.out_adjacent_entry_ = block_remove_from_graph(adjacent);
|
||||
}
|
||||
} else if self.we_removed_out_adjacent_ {
|
||||
if let Some(c) = self.out_adjacent_remove_command_.as_mut() {
|
||||
// We removed this, so we can restore it now
|
||||
c.undo_now();
|
||||
}
|
||||
|
||||
track_insert_block_after(
|
||||
&self.track,
|
||||
adjacent,
|
||||
Some(self.blocks.last().expect("non-empty blocks")),
|
||||
);
|
||||
} else {
|
||||
// Simply resize the adjacent
|
||||
block_set_length_and_media_in(
|
||||
self.out_adjacent.clone(),
|
||||
block_length(self.out_adjacent.clone()) + self.movement,
|
||||
adjacent,
|
||||
block_length(adjacent) + self.movement,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrap as an oakundo vtable command handle.
|
||||
pub fn to_command(self) -> CHandle {
|
||||
/// Wrap as an oakundo vtable command value.
|
||||
pub fn to_command(self) -> UndoCommand {
|
||||
box_command(self)
|
||||
}
|
||||
}
|
||||
@@ -591,18 +540,17 @@ impl Command for TrackSlideCommand {
|
||||
/// extends past the end of the sequence (timelineundopointer.h).
|
||||
pub struct TrackPlaceBlockCommand {
|
||||
/// Track list (`timeline_`).
|
||||
timeline: CHandle,
|
||||
timeline: NodeRef,
|
||||
/// Target track index (`track_index_`).
|
||||
track_index: i32,
|
||||
/// Placement in point (`in_`).
|
||||
in_: Rational,
|
||||
/// Gap inserted when the block extends past the end of the sequence (`gap_`).
|
||||
gap_: CHandle,
|
||||
/// Whether `gap_` is detached from the graph (`gap_orphaned_`); a detached
|
||||
/// handle is owned by this command.
|
||||
gap_orphaned_: bool,
|
||||
gap_: Option<NodeRef>,
|
||||
/// Arena entry of `gap_` while detached from the graph.
|
||||
gap_entry_: Option<NodeEntry>,
|
||||
/// Block to place (C++ `insert_`).
|
||||
block: CHandle,
|
||||
block: NodeRef,
|
||||
/// Track-add commands created when the track index is out of range
|
||||
/// (`add_track_commands_`).
|
||||
add_track_commands_: Vec<TimelineAddTrackCommand>,
|
||||
@@ -612,13 +560,15 @@ pub struct TrackPlaceBlockCommand {
|
||||
|
||||
impl TrackPlaceBlockCommand {
|
||||
/// Construct from track list + track index + block + in point.
|
||||
pub fn new(timeline: CHandle, track_index: i32, block: CHandle, in_: Rational) -> Self {
|
||||
///
|
||||
/// New signature (single-lib): `pub fn new(timeline: NodeRef, track_index: i32, block: NodeRef, in_: Rational) -> TrackPlaceBlockCommand`
|
||||
pub fn new(timeline: NodeRef, track_index: i32, block: NodeRef, in_: Rational) -> Self {
|
||||
Self {
|
||||
timeline,
|
||||
track_index,
|
||||
in_,
|
||||
gap_: CHandle::null(),
|
||||
gap_orphaned_: false,
|
||||
gap_: None,
|
||||
gap_entry_: None,
|
||||
block,
|
||||
add_track_commands_: Vec::new(),
|
||||
ripple_remove_command_: None,
|
||||
@@ -628,10 +578,7 @@ impl TrackPlaceBlockCommand {
|
||||
/// `redo`: place the block destructively.
|
||||
pub fn redo(&mut self) {
|
||||
// Determine if we need to add tracks
|
||||
let mut track_count = 0;
|
||||
// SAFETY: `track_count` is a valid out pointer.
|
||||
let _ =
|
||||
unsafe { oaknode_tracklist_get_track_count(self.timeline.clone(), &mut track_count) };
|
||||
let track_count = tracklist_track_count(&self.timeline) as i32;
|
||||
|
||||
if self.track_index >= track_count {
|
||||
if self.add_track_commands_.is_empty() {
|
||||
@@ -647,41 +594,37 @@ impl TrackPlaceBlockCommand {
|
||||
}
|
||||
}
|
||||
|
||||
let mut track = CHandle::null();
|
||||
// SAFETY: `track` is a valid out pointer.
|
||||
let _ = unsafe {
|
||||
oaknode_tracklist_get_track_at(self.timeline.clone(), self.track_index, &mut track)
|
||||
let Some(track) = tracklist_track_at(&self.timeline, self.track_index as usize) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let in_ = self.in_;
|
||||
let append = in_ >= track_length(track.clone());
|
||||
let append = in_ >= track_length(&track);
|
||||
|
||||
// Check if the placement location is past the end of the timeline
|
||||
if append {
|
||||
if in_ > track_length(track.clone()) {
|
||||
// If so, insert a gap here. In the module world the gap's in point
|
||||
// is stored on the gap (Olive derives positions from the track
|
||||
// order), so it must be length-set IN-anchored — an out-anchored
|
||||
// `set_length_and_media_out` on the fresh (in = 0) gap would push
|
||||
// the in point negative.
|
||||
if self.gap_.is_null() {
|
||||
// SAFETY: `oaknode_block_gap_create` returns a fresh owned handle.
|
||||
self.gap_ = unsafe { oaknode_block_gap_create() };
|
||||
block_set_length_and_media_in(
|
||||
self.gap_.clone(),
|
||||
in_ - track_length(track.clone()),
|
||||
);
|
||||
if in_ > track_length(&track) {
|
||||
// If so, insert a gap filling [track end, in_): the module
|
||||
// stores the gap's span on the block, so both ends are
|
||||
// written explicitly.
|
||||
if self.gap_.is_none() {
|
||||
self.gap_ = Some(block_gap_create(&self.timeline.project));
|
||||
if let Some(gap) = &self.gap_ {
|
||||
block_set_in(gap, track_length(&track));
|
||||
block_set_length_and_media_in(gap, in_ - track_length(&track));
|
||||
}
|
||||
}
|
||||
if let Some(gap) = &self.gap_ {
|
||||
block_add_to_graph(gap, self.gap_entry_.take());
|
||||
track_append_block(&track, gap);
|
||||
}
|
||||
block_add_to_graph(self.gap_.clone(), track.clone());
|
||||
track_append_block(track.clone(), self.gap_.clone());
|
||||
self.gap_orphaned_ = false;
|
||||
}
|
||||
|
||||
track_append_block(track, self.block.clone());
|
||||
track_append_block(&track, &self.block);
|
||||
} else {
|
||||
// Place the block at this point
|
||||
if self.ripple_remove_command_.is_none() {
|
||||
let insert_length = block_length(self.block.clone());
|
||||
let insert_length = block_length(&self.block);
|
||||
let mut cmd = TrackRippleRemoveAreaCommand::new(
|
||||
track.clone(),
|
||||
TimeRange::new(in_, in_ + insert_length),
|
||||
@@ -694,39 +637,33 @@ impl TrackPlaceBlockCommand {
|
||||
cmd.prepare();
|
||||
cmd.redo();
|
||||
|
||||
// Insert after the block that follows the cleared area (`blocks` is
|
||||
// non-empty when the command was constructed validly)
|
||||
// Insert after the block that follows the cleared area
|
||||
let index = self
|
||||
.ripple_remove_command_
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.get_insertion_index();
|
||||
// SAFETY: valid handles; `index` may be empty when the insertion is at
|
||||
// the front of the track, which the ABI tolerates like C++.
|
||||
let _ = unsafe { oaknode_track_insert_block_after(track, self.block.clone(), index) };
|
||||
track_insert_block_after(&track, &self.block, index.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
/// `undo`: remove the placed block and restore displaced blocks.
|
||||
pub fn undo(&mut self) {
|
||||
let mut t = CHandle::null();
|
||||
// SAFETY: `t` is a valid out pointer.
|
||||
let _ = unsafe {
|
||||
oaknode_tracklist_get_track_at(self.timeline.clone(), self.track_index, &mut t)
|
||||
let Some(t) = tracklist_track_at(&self.timeline, self.track_index as usize) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Firstly, remove our insert
|
||||
// SAFETY: valid handles.
|
||||
let _ = unsafe { oaknode_track_ripple_remove_block(t.clone(), self.block.clone()) };
|
||||
track_ripple_remove_block(&t, &self.block);
|
||||
|
||||
if self.ripple_remove_command_.is_some() {
|
||||
// If we ripple-removed, just undo that
|
||||
self.ripple_remove_command_.as_mut().unwrap().undo();
|
||||
} else if !self.gap_.is_null() {
|
||||
// SAFETY: valid handles.
|
||||
let _ = unsafe { oaknode_track_ripple_remove_block(t.clone(), self.gap_.clone()) };
|
||||
block_remove_from_graph(self.gap_.clone(), t.clone());
|
||||
self.gap_orphaned_ = true;
|
||||
} else if let Some(gap) = &self.gap_ {
|
||||
track_ripple_remove_block(&t, gap);
|
||||
if self.gap_entry_.is_none() {
|
||||
self.gap_entry_ = block_remove_from_graph(gap);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove tracks if we added them
|
||||
@@ -735,8 +672,8 @@ impl TrackPlaceBlockCommand {
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrap as an oakundo vtable command handle.
|
||||
pub fn to_command(self) -> CHandle {
|
||||
/// Wrap as an oakundo vtable command value.
|
||||
pub fn to_command(self) -> UndoCommand {
|
||||
box_command(self)
|
||||
}
|
||||
}
|
||||
@@ -768,7 +705,7 @@ pub struct TrackMoveBlockCommand {
|
||||
/// Composite of the gap + place halves (`children_`).
|
||||
children: MultiUndoCommand,
|
||||
/// The block being moved (`block_`).
|
||||
block: CHandle,
|
||||
block: NodeRef,
|
||||
/// In point captured at construction (`old_in_`).
|
||||
old_in: Rational,
|
||||
/// Destination in point (`in_`).
|
||||
@@ -777,29 +714,34 @@ pub struct TrackMoveBlockCommand {
|
||||
|
||||
impl TrackMoveBlockCommand {
|
||||
/// Construct from track list + destination track index + block + in point.
|
||||
pub fn new(timeline: CHandle, track_index: i32, block: CHandle, in_: Rational) -> Self {
|
||||
///
|
||||
/// New signature (single-lib): `pub fn new(timeline: NodeRef, track_index: i32, block: NodeRef, in_: Rational) -> TrackMoveBlockCommand`
|
||||
pub fn new(timeline: NodeRef, track_index: i32, block: NodeRef, in_: Rational) -> Self {
|
||||
let mut children = MultiUndoCommand::new();
|
||||
children.add_child(Box::new(TrackReplaceBlockWithGapCommand::new(
|
||||
block_track(block.clone()),
|
||||
block.clone(),
|
||||
true,
|
||||
)));
|
||||
if let Some(track) = block_track(&block) {
|
||||
children.add_child(Box::new(TrackReplaceBlockWithGapCommand::new(
|
||||
track,
|
||||
block.clone(),
|
||||
true,
|
||||
)));
|
||||
}
|
||||
children.add_child(Box::new(TrackPlaceBlockCommand::new(
|
||||
timeline,
|
||||
track_index,
|
||||
block.clone(),
|
||||
in_,
|
||||
)));
|
||||
let old_in = block_in(&block);
|
||||
Self {
|
||||
children,
|
||||
block,
|
||||
old_in: block_in(block.clone()),
|
||||
old_in,
|
||||
in_,
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrap as an oakundo vtable command handle.
|
||||
pub fn to_command(self) -> CHandle {
|
||||
/// Wrap as an oakundo vtable command value.
|
||||
pub fn to_command(self) -> UndoCommand {
|
||||
box_command(self)
|
||||
}
|
||||
}
|
||||
@@ -809,13 +751,13 @@ impl Command for TrackMoveBlockCommand {
|
||||
fn redo(&mut self) {
|
||||
// The module stores the block's position on the block itself (no
|
||||
// track-order derivation), so re-home it before placing.
|
||||
block_set_in(self.block.clone(), self.in_);
|
||||
block_set_in(&self.block, self.in_);
|
||||
self.children.redo();
|
||||
}
|
||||
|
||||
/// `Command::undo` — the composite runs children in reverse.
|
||||
fn undo(&mut self) {
|
||||
self.children.undo();
|
||||
block_set_in(self.block.clone(), self.old_in);
|
||||
block_set_in(&self.block, self.old_in);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,132 +14,173 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Split commands (`src/timeline/src/timelineundosplit.h`). All graph
|
||||
//! operations go through the oaknode C ABI.
|
||||
//! Split commands (`src/timeline/src/timelineundosplit.h`). Graph
|
||||
//! operations go directly through the oaknode Rust domain (the oaknode
|
||||
//! C ABI was deleted in the single-lib unification).
|
||||
//!
|
||||
//! The C++ oracles (`timelineundosplit.cpp`) clone the split block in the
|
||||
//! project graph (`oaknode_node_copy_in_graph` + `oaknode_block_from_node`),
|
||||
//! run a captured node-graph reconnect command, preserve link groups
|
||||
//! (`oaknode_block_are_linked`), move out-transitions across the split
|
||||
//! (`oaknode_node_disconnect`/`oaknode_node_connect`), and add cache
|
||||
//! passthrough (`oaknode_clip_add_cache_passthrough_from`). None of those
|
||||
//! symbols are exposed by this crate's oaknode bridge, so those branches are
|
||||
//! omitted here and noted at each site; the remaining length/insert/remove
|
||||
//! logic mirrors the C++.
|
||||
//! passthrough (`oaknode_clip_add_cache_passthrough_from`). The clone is
|
||||
//! restored via `NodeBehavior::duplicate` (the Rust equivalent of
|
||||
//! `Node::Copy`) and link preservation via `Graph::link`; the
|
||||
//! transition/cache/reconnect steps have no Rust equivalent yet and are
|
||||
//! noted at each site.
|
||||
|
||||
use oakcore_rs::Rational;
|
||||
use oaknode::graph::NodeEntry;
|
||||
use oakundo::undocommand::UndoCommand;
|
||||
|
||||
use crate::bridge::node::{
|
||||
oaknode_block_clip_create, oaknode_track_insert_block_after, oaknode_track_ripple_remove_block,
|
||||
};
|
||||
use crate::handle::CHandle;
|
||||
use crate::util::{
|
||||
block_in, block_length, block_out, block_set_length_and_media_in,
|
||||
block_set_length_and_media_out, block_track, same_block,
|
||||
block_add_to_graph, block_in, block_length, block_out, block_remove_from_graph,
|
||||
block_set_length_and_media_in, block_set_length_and_media_out, block_track,
|
||||
track_insert_block_after, track_ripple_remove_block, GraphBlockRange, NodeRef,
|
||||
};
|
||||
|
||||
/// `BlockSplitCommand` — split one block at a point
|
||||
/// (timelineundosplit.h).
|
||||
///
|
||||
/// The split keeps the original block anchored at its in-point (it becomes
|
||||
/// the first half `[in, point)`) and anchors the cloned `new_block` at its
|
||||
/// out-point (it becomes the second half `[point, out)`), inserted right
|
||||
/// after the original so the track order mirrors the timeline order.
|
||||
pub struct BlockSplitCommand {
|
||||
/// Block to split.
|
||||
block: CHandle,
|
||||
block: NodeRef,
|
||||
/// Split point.
|
||||
point: Rational,
|
||||
/// Second block created by the split (valid after `redo`).
|
||||
new_block: CHandle,
|
||||
new_block: Option<NodeRef>,
|
||||
/// Arena entry of the second block while it is detached from the
|
||||
/// graph (between `undo` and the next `redo`).
|
||||
new_block_entry: Option<NodeEntry>,
|
||||
/// Length of `block` before the split, restored on `undo`.
|
||||
old_length: Rational,
|
||||
}
|
||||
|
||||
impl BlockSplitCommand {
|
||||
/// Construct from block + split point.
|
||||
pub fn new(block: CHandle, point: Rational) -> Self {
|
||||
///
|
||||
/// New signature (single-lib): `pub fn new(block: NodeRef, point: Rational) -> BlockSplitCommand`
|
||||
pub fn new(block: NodeRef, point: Rational) -> Self {
|
||||
Self {
|
||||
block,
|
||||
point,
|
||||
new_block: CHandle::null(),
|
||||
new_block: None,
|
||||
new_block_entry: None,
|
||||
old_length: Rational::new(0, 1),
|
||||
}
|
||||
}
|
||||
|
||||
/// `prepare`: create the second half of the split. The C++ clone is
|
||||
/// performed by `oaknode_node_copy_in_graph` + `oaknode_block_from_node`,
|
||||
/// which are absent from this bridge, so a fresh clip block is created.
|
||||
/// `prepare`: create the second half by cloning the original block in
|
||||
/// the project graph (the Rust equivalent of the C++
|
||||
/// `oaknode_node_copy_in_graph`; the clone happens through the
|
||||
/// behavior's `duplicate`, which copies the block core too).
|
||||
pub fn prepare(&mut self) {
|
||||
if self.new_block.is_null() {
|
||||
// SAFETY: `oaknode_block_clip_create` returns an owned handle.
|
||||
self.new_block = unsafe { oaknode_block_clip_create() };
|
||||
if self.new_block.is_some() {
|
||||
return;
|
||||
}
|
||||
let id = {
|
||||
let project = self.block.project.clone();
|
||||
let mut p = project.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
let entry = match p.graph.get(self.block.id) {
|
||||
Some(e) => e,
|
||||
None => return,
|
||||
};
|
||||
let new_core = entry.core.clone();
|
||||
let new_behavior = match entry.behavior.duplicate(&entry.core) {
|
||||
Some(b) => b,
|
||||
None => return,
|
||||
};
|
||||
p.graph.add_node(new_core, new_behavior)
|
||||
};
|
||||
self.new_block = Some(NodeRef::new(self.block.project.clone(), id));
|
||||
}
|
||||
|
||||
/// `redo`: shrink `block` to the first half, grow `new_block` to the
|
||||
/// second half, and insert it after `block`.
|
||||
///
|
||||
/// The split keeps the ORIGINAL block anchored at its out-point (it
|
||||
/// becomes the second half `[point, out)`) and anchors the fresh
|
||||
/// `new_block` at its in-point (it becomes the first half
|
||||
/// `[in, point)`), matching the C++ `Block::set_length_and_media_out` /
|
||||
/// `set_length_and_media_in` semantics of the module.
|
||||
/// The split keeps the ORIGINAL block anchored at its in-point (the
|
||||
/// C++ `set_length_and_media_in` on the original) and anchors the
|
||||
/// cloned `new_block` at its out-point (the C++
|
||||
/// `set_length_and_media_out` on the copy — the copy carried the
|
||||
/// original span, so the out point is preserved exactly).
|
||||
pub fn redo(&mut self) {
|
||||
// Create the second half if redo is invoked without a preceding
|
||||
// prepare() (the C ABI command path may call redo directly).
|
||||
if self.new_block.is_null() {
|
||||
// SAFETY: `oaknode_block_clip_create` returns an owned handle.
|
||||
self.new_block = unsafe { oaknode_block_clip_create() };
|
||||
}
|
||||
// prepare() (the vtable command path may call redo directly).
|
||||
self.prepare();
|
||||
|
||||
self.old_length = block_length(self.block.clone());
|
||||
// The C++ asserts `point_` lies strictly inside the block; that
|
||||
// would panic across the FFI boundary, so it is intentionally not
|
||||
// replicated — a split point outside the span is clamped by the
|
||||
// rational arithmetic below.
|
||||
self.old_length = block_length(&self.block);
|
||||
|
||||
let block_in = block_in(self.block.clone());
|
||||
let block_out = block_out(self.block.clone());
|
||||
let block_in = block_in(&self.block);
|
||||
let block_out = block_out(&self.block);
|
||||
|
||||
// The C++ asserts `point_` lies strictly inside the block; that would
|
||||
// panic across the FFI boundary, so it is intentionally not replicated.
|
||||
let first_half_length = self.point - block_in;
|
||||
let second_half_length = block_out - self.point;
|
||||
|
||||
let track = block_track(self.block.clone());
|
||||
if let Some(new_block) = &self.new_block {
|
||||
// Re-attach the second half if a previous undo detached it.
|
||||
if self.new_block_entry.is_some() {
|
||||
block_add_to_graph(new_block, self.new_block_entry.take());
|
||||
}
|
||||
// In-anchored length for the first half keeps the original's in
|
||||
// point (the C++ `set_length_and_media_in`); the out-anchored
|
||||
// length for the second half keeps the copy's out point (the
|
||||
// C++ `set_length_and_media_out`).
|
||||
block_set_length_and_media_in(&self.block, first_half_length);
|
||||
block_set_length_and_media_out(new_block, second_half_length);
|
||||
|
||||
// Out-anchored length for the second half keeps the original's out
|
||||
// point (the C++ `set_length_and_media_out`); the in-anchored length
|
||||
// for the first half grows the fresh block from its default in of 0
|
||||
// (the C++ `set_length_and_media_in`). The two were previously
|
||||
// swapped, which anchored the halves at the wrong points.
|
||||
block_set_length_and_media_out(self.block.clone(), second_half_length);
|
||||
block_set_length_and_media_in(self.new_block.clone(), first_half_length);
|
||||
|
||||
// SAFETY: bridge inserts `new_block` after `block` on `track`.
|
||||
let _ = unsafe {
|
||||
oaknode_track_insert_block_after(track, self.new_block.clone(), self.block.clone())
|
||||
};
|
||||
if let Some(track) = block_track(&self.block) {
|
||||
track_insert_block_after(&track, new_block, Some(&self.block));
|
||||
}
|
||||
}
|
||||
|
||||
// The C++ also re-runs a `reconnect_tree_command_` (the node-graph
|
||||
// re-connection captured while cloning) and moves an out transition
|
||||
// onto `new_block` via `oaknode_*` connection APIs; those symbols are
|
||||
// absent from this bridge, so both are omitted here.
|
||||
// onto `new_block` via `oaknode_*` connection APIs; the Rust block
|
||||
// has no transition edges, so both are omitted here.
|
||||
}
|
||||
|
||||
/// `undo`: restore `block`'s original length and remove the second half.
|
||||
pub fn undo(&mut self) {
|
||||
let track = block_track(self.block.clone());
|
||||
|
||||
block_set_length_and_media_out(self.block.clone(), self.old_length);
|
||||
|
||||
// SAFETY: bridge ripple-removes `new_block` from `track`.
|
||||
let _ = unsafe { oaknode_track_ripple_remove_block(track, self.new_block.clone()) };
|
||||
if let Some(track) = block_track(&self.block) {
|
||||
// The redo shrank the original from its in point (it became the
|
||||
// first half, in-anchored), so the restore grows it in-anchored
|
||||
// too — the module stores the span on the block (the C++
|
||||
// `set_length_and_media_out` derives positions from the track
|
||||
// order and does not apply here).
|
||||
block_set_length_and_media_in(&self.block, self.old_length);
|
||||
if let Some(new_block) = &self.new_block {
|
||||
track_ripple_remove_block(&track, new_block);
|
||||
// Detach the second half from the graph (the C++ re-parents
|
||||
// it to the scratch memory manager); the entry is owned by
|
||||
// this command until the next redo.
|
||||
if self.new_block_entry.is_none() {
|
||||
self.new_block_entry = block_remove_from_graph(new_block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The C++ first moves a previously-moved out transition back onto
|
||||
// `block` and runs `reconnect_tree_command_`'s undo; both need oaknode
|
||||
// symbols absent from this bridge, so they are omitted here.
|
||||
// `block` and runs `reconnect_tree_command_`'s undo; the Rust block
|
||||
// has no transition edges, so those are omitted here.
|
||||
}
|
||||
|
||||
/// The second block created by the split; only valid after `redo`.
|
||||
pub fn new_block(&self) -> CHandle {
|
||||
///
|
||||
/// New signature (single-lib): `pub fn new_block(&self) -> Option<NodeRef>`
|
||||
pub fn new_block(&self) -> Option<NodeRef> {
|
||||
self.new_block.clone()
|
||||
}
|
||||
|
||||
/// Wrap as an oakundo vtable command handle.
|
||||
pub fn to_command(self) -> CHandle {
|
||||
/// Wrap as an oakundo vtable command value.
|
||||
pub fn to_command(self) -> UndoCommand {
|
||||
crate::undocommon::box_command(self)
|
||||
}
|
||||
}
|
||||
@@ -158,19 +199,21 @@ impl crate::undocommon::Command for BlockSplitCommand {
|
||||
/// times, preserving link groups (timelineundosplit.h).
|
||||
pub struct BlockSplitPreservingLinksCommand {
|
||||
/// Blocks to split.
|
||||
blocks: Vec<CHandle>,
|
||||
blocks: Vec<NodeRef>,
|
||||
/// Split times.
|
||||
times: Vec<Rational>,
|
||||
/// Child split commands, executed in order on `redo`.
|
||||
commands: Vec<BlockSplitCommand>,
|
||||
/// `splits[time_index][block_index]`: second half created for that
|
||||
/// block/time, or an empty handle when the block was not split there.
|
||||
splits: Vec<Vec<CHandle>>,
|
||||
/// block/time, or `None` when the block was not split there.
|
||||
splits: Vec<Vec<Option<NodeRef>>>,
|
||||
}
|
||||
|
||||
impl BlockSplitPreservingLinksCommand {
|
||||
/// Construct from blocks + times (one per block).
|
||||
pub fn new(blocks: Vec<CHandle>, times: Vec<Rational>) -> Self {
|
||||
///
|
||||
/// New signature (single-lib): `pub fn new(blocks: Vec<NodeRef>, times: Vec<Rational>) -> BlockSplitPreservingLinksCommand`
|
||||
pub fn new(blocks: Vec<NodeRef>, times: Vec<Rational>) -> Self {
|
||||
Self {
|
||||
blocks,
|
||||
times,
|
||||
@@ -183,7 +226,7 @@ impl BlockSplitPreservingLinksCommand {
|
||||
pub fn prepare(&mut self) {
|
||||
let n_times = self.times.len();
|
||||
let n_blocks = self.blocks.len();
|
||||
self.splits = vec![vec![CHandle::null(); n_blocks]; n_times];
|
||||
self.splits = vec![vec![None; n_blocks]; n_times];
|
||||
|
||||
for i in 0..n_times {
|
||||
let time = self.times[i];
|
||||
@@ -192,8 +235,8 @@ impl BlockSplitPreservingLinksCommand {
|
||||
// panic across the FFI boundary, so it is not replicated.
|
||||
|
||||
for j in 0..n_blocks {
|
||||
let b_in = block_in(self.blocks[j].clone());
|
||||
let b_out = block_out(self.blocks[j].clone());
|
||||
let b_in = block_in(&self.blocks[j]);
|
||||
let b_out = block_out(&self.blocks[j]);
|
||||
|
||||
if b_in < time && b_out > time {
|
||||
let mut split = BlockSplitCommand::new(self.blocks[j].clone(), time);
|
||||
@@ -205,19 +248,35 @@ impl BlockSplitPreservingLinksCommand {
|
||||
}
|
||||
}
|
||||
|
||||
// The C++ then relinks every pair of originally-linked blocks by
|
||||
// creating link commands between their split halves
|
||||
// (`oaknode_block_are_linked`); that symbol is absent from this
|
||||
// bridge, so link preservation is omitted.
|
||||
// Link preservation (C++ `oaknode_block_are_linked` +
|
||||
// link commands between the halves): every pair of originally
|
||||
// linked blocks that was split at the same time has its new
|
||||
// halves linked too (`Graph::link` mirrors `Node::link`).
|
||||
let Some(first) = self.blocks.first() else {
|
||||
return;
|
||||
};
|
||||
let project = first.project.clone();
|
||||
let mut p = project.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
for i in 0..n_times {
|
||||
for j in 0..n_blocks {
|
||||
for k in (j + 1)..n_blocks {
|
||||
if p.graph.are_linked(self.blocks[j].id, self.blocks[k].id) {
|
||||
if let (Some(a), Some(b)) = (&self.splits[i][j], &self.splits[i][k]) {
|
||||
p.graph.link(a.id, b.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `redo`: redo every child command in order.
|
||||
///
|
||||
/// The C ABI command path never invokes `prepare()` (the oakundo vtable
|
||||
/// wrapper only dispatches `redo`/`undo`), so the children are built on
|
||||
/// first redo; `prepare` itself redoes each child as it builds it, so the
|
||||
/// first redo has nothing left to run. Later redos (after an undo) run
|
||||
/// the stored children directly.
|
||||
/// The vtable command path never invokes `prepare()` (the oakundo
|
||||
/// wrapper only dispatches `redo`/`undo`), so the children are built
|
||||
/// on first redo; `prepare` itself redoes each child as it builds it,
|
||||
/// so the first redo has nothing left to run. Later redos (after an
|
||||
/// undo) run the stored children directly.
|
||||
pub fn redo(&mut self) {
|
||||
if self.commands.is_empty() {
|
||||
self.prepare();
|
||||
@@ -237,23 +296,26 @@ impl BlockSplitPreservingLinksCommand {
|
||||
|
||||
/// The block produced by splitting `original` at `time_index`;
|
||||
/// `None` when not applicable (timelineundosplit.h `get_split`).
|
||||
pub fn get_split(&self, original: CHandle, time_index: usize) -> Option<CHandle> {
|
||||
///
|
||||
/// New signature (single-lib): `pub fn get_split(&self, original: &NodeRef, time_index: usize) -> Option<NodeRef>`
|
||||
pub fn get_split(&self, original: &NodeRef, time_index: usize) -> Option<NodeRef> {
|
||||
if time_index < self.times.len() {
|
||||
for (i, b) in self.blocks.iter().enumerate() {
|
||||
if same_block(b.clone(), original.clone()) {
|
||||
if b.id == original.id {
|
||||
return self
|
||||
.splits
|
||||
.get(time_index)
|
||||
.and_then(|row| row.get(i))
|
||||
.cloned();
|
||||
.cloned()
|
||||
.flatten();
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Wrap as an oakundo vtable command handle.
|
||||
pub fn to_command(self) -> CHandle {
|
||||
/// Wrap as an oakundo vtable command value.
|
||||
pub fn to_command(self) -> UndoCommand {
|
||||
crate::undocommon::box_command(self)
|
||||
}
|
||||
}
|
||||
@@ -272,7 +334,7 @@ impl crate::undocommon::Command for BlockSplitPreservingLinksCommand {
|
||||
/// (timelineundosplit.h).
|
||||
pub struct TrackSplitAtTimeCommand {
|
||||
/// Owning track.
|
||||
track: CHandle,
|
||||
track: NodeRef,
|
||||
/// Split point.
|
||||
point: Rational,
|
||||
/// Inner per-block split command, built by `prepare`.
|
||||
@@ -281,7 +343,9 @@ pub struct TrackSplitAtTimeCommand {
|
||||
|
||||
impl TrackSplitAtTimeCommand {
|
||||
/// Construct from track + point.
|
||||
pub fn new(track: CHandle, point: Rational) -> Self {
|
||||
///
|
||||
/// New signature (single-lib): `pub fn new(track: NodeRef, point: Rational) -> TrackSplitAtTimeCommand`
|
||||
pub fn new(track: NodeRef, point: Rational) -> Self {
|
||||
Self {
|
||||
track,
|
||||
point,
|
||||
@@ -289,17 +353,36 @@ impl TrackSplitAtTimeCommand {
|
||||
}
|
||||
}
|
||||
|
||||
/// `prepare`: build the per-block split command. The C++ finds the block
|
||||
/// via `oaknode_track_get_block_containing_time`, which is not exposed by
|
||||
/// this crate's oaknode bridge (and there is no block-enumeration helper
|
||||
/// to search for it), so the inner command cannot be built here and
|
||||
/// redo/undo remain no-ops until that lookup API exists.
|
||||
/// `prepare`: build the per-block split command. The C++ finds the
|
||||
/// block via `oaknode_track_get_block_containing_time`; the Rust
|
||||
/// equivalent is `TrackBehavior::block_containing_time` over the
|
||||
/// graph-backed block range.
|
||||
pub fn prepare(&mut self) {
|
||||
let _ = (self.track.clone(), self.point);
|
||||
if self.command.is_some() {
|
||||
return;
|
||||
}
|
||||
let block_id = {
|
||||
let project = self.track.project.clone();
|
||||
let p = project.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
p.graph
|
||||
.get(self.track.id)
|
||||
.and_then(|e| e.behavior.as_any())
|
||||
.and_then(|a| a.downcast_ref::<oaknode::track::TrackBehavior>())
|
||||
.and_then(|t| {
|
||||
t.block_containing_time(self.point, &GraphBlockRange { graph: &p.graph })
|
||||
})
|
||||
};
|
||||
if let Some(id) = block_id {
|
||||
self.command = Some(BlockSplitCommand::new(
|
||||
NodeRef::new(self.track.project.clone(), id),
|
||||
self.point,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// `redo`: forward to the inner command.
|
||||
pub fn redo(&mut self) {
|
||||
self.prepare();
|
||||
if let Some(c) = self.command.as_mut() {
|
||||
c.redo();
|
||||
}
|
||||
@@ -312,8 +395,8 @@ impl TrackSplitAtTimeCommand {
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrap as an oakundo vtable command handle.
|
||||
pub fn to_command(self) -> CHandle {
|
||||
/// Wrap as an oakundo vtable command value.
|
||||
pub fn to_command(self) -> UndoCommand {
|
||||
crate::undocommon::box_command(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,58 +15,55 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Track-level block commands (`src/timeline/src/timelineundotrack.h`).
|
||||
//! All graph operations go through the oaknode C ABI (`bridge::node`).
|
||||
//! Graph operations go directly through the oaknode Rust domain
|
||||
//! ([`crate::util::NodeRef`] + the project graph) since the single-lib
|
||||
//! unification removed the oaknode C ABI.
|
||||
|
||||
use oakundo::undocommand::UndoCommand;
|
||||
|
||||
use crate::bridge::node::{
|
||||
oaknode_block_get_previous, oaknode_track_insert_block_after, oaknode_track_prepend_block,
|
||||
oaknode_track_replace_block, oaknode_track_ripple_remove_block,
|
||||
};
|
||||
use crate::handle::CHandle;
|
||||
use crate::undocommon::{box_command, Command};
|
||||
use crate::util::{
|
||||
block_previous, track_insert_block_after, track_prepend_block, track_replace_block,
|
||||
track_ripple_remove_block, NodeRef,
|
||||
};
|
||||
|
||||
/// `TrackRippleRemoveBlockCommand` — ripple-remove a block from a track
|
||||
/// (timelineundotrack.h).
|
||||
pub struct TrackRippleRemoveBlockCommand {
|
||||
/// Owning track.
|
||||
track: CHandle,
|
||||
track: NodeRef,
|
||||
/// Block to remove.
|
||||
block: CHandle,
|
||||
block: NodeRef,
|
||||
/// Predecessor of the removed block, captured at `redo` time so `undo`
|
||||
/// can restore the block at its original position.
|
||||
before: CHandle,
|
||||
before: Option<NodeRef>,
|
||||
}
|
||||
|
||||
impl TrackRippleRemoveBlockCommand {
|
||||
/// Construct from track + block.
|
||||
pub fn new(track: CHandle, block: CHandle) -> Self {
|
||||
///
|
||||
/// New signature (single-lib): `pub fn new(track: NodeRef, block: NodeRef) -> TrackRippleRemoveBlockCommand`
|
||||
pub fn new(track: NodeRef, block: NodeRef) -> Self {
|
||||
Self {
|
||||
track,
|
||||
block,
|
||||
before: CHandle::null(),
|
||||
before: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// `redo`: capture the predecessor, then `oaknode_track_ripple_remove_block`.
|
||||
/// `redo`: capture the predecessor, then ripple-remove.
|
||||
pub fn redo(&mut self) {
|
||||
unsafe {
|
||||
oaknode_block_get_previous(self.block.clone(), &mut self.before);
|
||||
oaknode_track_ripple_remove_block(self.track.clone(), self.block.clone());
|
||||
}
|
||||
self.before = block_previous(&self.block);
|
||||
track_ripple_remove_block(&self.track, &self.block);
|
||||
}
|
||||
|
||||
/// `undo`: re-insert via `oaknode_track_insert_block_after`.
|
||||
/// `undo`: re-insert after the captured predecessor.
|
||||
pub fn undo(&mut self) {
|
||||
unsafe {
|
||||
oaknode_track_insert_block_after(
|
||||
self.track.clone(),
|
||||
self.block.clone(),
|
||||
self.before.clone(),
|
||||
);
|
||||
}
|
||||
track_insert_block_after(&self.track, &self.block, self.before.as_ref());
|
||||
}
|
||||
|
||||
/// Wrap as an oakundo vtable command handle.
|
||||
pub fn to_command(self) -> CHandle {
|
||||
/// Wrap as an oakundo vtable command value.
|
||||
pub fn to_command(self) -> UndoCommand {
|
||||
box_command(self)
|
||||
}
|
||||
}
|
||||
@@ -87,33 +84,31 @@ impl Command for TrackRippleRemoveBlockCommand {
|
||||
/// (timelineundotrack.h).
|
||||
pub struct TrackPrependBlockCommand {
|
||||
/// Owning track.
|
||||
track: CHandle,
|
||||
track: NodeRef,
|
||||
/// Block to prepend.
|
||||
block: CHandle,
|
||||
block: NodeRef,
|
||||
}
|
||||
|
||||
impl TrackPrependBlockCommand {
|
||||
/// Construct from track + block.
|
||||
pub fn new(track: CHandle, block: CHandle) -> Self {
|
||||
///
|
||||
/// New signature (single-lib): `pub fn new(track: NodeRef, block: NodeRef) -> TrackPrependBlockCommand`
|
||||
pub fn new(track: NodeRef, block: NodeRef) -> Self {
|
||||
Self { track, block }
|
||||
}
|
||||
|
||||
/// `redo`: `oaknode_track_prepend_block`.
|
||||
/// `redo`: prepend the block.
|
||||
pub fn redo(&mut self) {
|
||||
unsafe {
|
||||
oaknode_track_prepend_block(self.track.clone(), self.block.clone());
|
||||
}
|
||||
track_prepend_block(&self.track, &self.block);
|
||||
}
|
||||
|
||||
/// `undo`: `oaknode_track_ripple_remove_block`.
|
||||
/// `undo`: ripple-remove the block.
|
||||
pub fn undo(&mut self) {
|
||||
unsafe {
|
||||
oaknode_track_ripple_remove_block(self.track.clone(), self.block.clone());
|
||||
}
|
||||
track_ripple_remove_block(&self.track, &self.block);
|
||||
}
|
||||
|
||||
/// Wrap as an oakundo vtable command handle.
|
||||
pub fn to_command(self) -> CHandle {
|
||||
/// Wrap as an oakundo vtable command value.
|
||||
pub fn to_command(self) -> UndoCommand {
|
||||
box_command(self)
|
||||
}
|
||||
}
|
||||
@@ -131,19 +126,22 @@ impl Command for TrackPrependBlockCommand {
|
||||
}
|
||||
|
||||
/// `TrackInsertBlockAfterCommand` — insert a block after a predecessor
|
||||
/// (timelineundotrack.h).
|
||||
/// (timelineundotrack.h). A `None` predecessor inserts at the front of
|
||||
/// the track.
|
||||
pub struct TrackInsertBlockAfterCommand {
|
||||
/// Owning track.
|
||||
track: CHandle,
|
||||
track: NodeRef,
|
||||
/// Block to insert.
|
||||
block: CHandle,
|
||||
/// Predecessor block.
|
||||
before: CHandle,
|
||||
block: NodeRef,
|
||||
/// Predecessor block (`None` = front).
|
||||
before: Option<NodeRef>,
|
||||
}
|
||||
|
||||
impl TrackInsertBlockAfterCommand {
|
||||
/// Construct from track + block + predecessor.
|
||||
pub fn new(track: CHandle, block: CHandle, before: CHandle) -> Self {
|
||||
///
|
||||
/// New signature (single-lib): `pub fn new(track: NodeRef, block: NodeRef, before: Option<NodeRef>) -> TrackInsertBlockAfterCommand`
|
||||
pub fn new(track: NodeRef, block: NodeRef, before: Option<NodeRef>) -> Self {
|
||||
Self {
|
||||
track,
|
||||
block,
|
||||
@@ -151,26 +149,18 @@ impl TrackInsertBlockAfterCommand {
|
||||
}
|
||||
}
|
||||
|
||||
/// `redo`: `oaknode_track_insert_block_after`.
|
||||
/// `redo`: insert after the predecessor.
|
||||
pub fn redo(&mut self) {
|
||||
unsafe {
|
||||
oaknode_track_insert_block_after(
|
||||
self.track.clone(),
|
||||
self.block.clone(),
|
||||
self.before.clone(),
|
||||
);
|
||||
}
|
||||
track_insert_block_after(&self.track, &self.block, self.before.as_ref());
|
||||
}
|
||||
|
||||
/// `undo`: `oaknode_track_ripple_remove_block`.
|
||||
/// `undo`: ripple-remove the block.
|
||||
pub fn undo(&mut self) {
|
||||
unsafe {
|
||||
oaknode_track_ripple_remove_block(self.track.clone(), self.block.clone());
|
||||
}
|
||||
track_ripple_remove_block(&self.track, &self.block);
|
||||
}
|
||||
|
||||
/// Wrap as an oakundo vtable command handle.
|
||||
pub fn to_command(self) -> CHandle {
|
||||
/// Wrap as an oakundo vtable command value.
|
||||
pub fn to_command(self) -> UndoCommand {
|
||||
box_command(self)
|
||||
}
|
||||
}
|
||||
@@ -191,16 +181,18 @@ impl Command for TrackInsertBlockAfterCommand {
|
||||
/// (equal lengths required) (timelineundotrack.h).
|
||||
pub struct TrackReplaceBlockCommand {
|
||||
/// Owning track.
|
||||
track: CHandle,
|
||||
track: NodeRef,
|
||||
/// Block to replace.
|
||||
old: CHandle,
|
||||
old: NodeRef,
|
||||
/// Replacement block.
|
||||
replace: CHandle,
|
||||
replace: NodeRef,
|
||||
}
|
||||
|
||||
impl TrackReplaceBlockCommand {
|
||||
/// Construct from track + old + replace.
|
||||
pub fn new(track: CHandle, old: CHandle, replace: CHandle) -> Self {
|
||||
///
|
||||
/// New signature (single-lib): `pub fn new(track: NodeRef, old: NodeRef, replace: NodeRef) -> TrackReplaceBlockCommand`
|
||||
pub fn new(track: NodeRef, old: NodeRef, replace: NodeRef) -> Self {
|
||||
Self {
|
||||
track,
|
||||
old,
|
||||
@@ -208,22 +200,18 @@ impl TrackReplaceBlockCommand {
|
||||
}
|
||||
}
|
||||
|
||||
/// `redo`: `oaknode_track_replace_block(track, old, replace)`.
|
||||
/// `redo`: replace `old` with `replace`.
|
||||
pub fn redo(&mut self) {
|
||||
unsafe {
|
||||
oaknode_track_replace_block(self.track.clone(), self.old.clone(), self.replace.clone());
|
||||
}
|
||||
track_replace_block(&self.track, &self.old, &self.replace);
|
||||
}
|
||||
|
||||
/// `undo`: `oaknode_track_replace_block(track, replace, old)`.
|
||||
/// `undo`: swap back.
|
||||
pub fn undo(&mut self) {
|
||||
unsafe {
|
||||
oaknode_track_replace_block(self.track.clone(), self.replace.clone(), self.old.clone());
|
||||
}
|
||||
track_replace_block(&self.track, &self.replace, &self.old);
|
||||
}
|
||||
|
||||
/// Wrap as an oakundo vtable command handle.
|
||||
pub fn to_command(self) -> CHandle {
|
||||
/// Wrap as an oakundo vtable command value.
|
||||
pub fn to_command(self) -> UndoCommand {
|
||||
box_command(self)
|
||||
}
|
||||
}
|
||||
|
||||
+645
-193
@@ -17,247 +17,699 @@
|
||||
//! Inline helpers from `src/timeline/src/timelineutil.h`: Rational ↔
|
||||
//! num/den pairs, value-handle identity, and block/track/project queries.
|
||||
//!
|
||||
//! All graph access goes through the oaknode C ABI (`bridge::node`). The
|
||||
//! C++ `oaknode_c_api::to_native` internals are NOT replicated — handles
|
||||
//! are opaque here; identity (`same_*`) is decided by `ctx` equality, not
|
||||
//! by `to_native` (borrowed accessors box a fresh handle per call).
|
||||
//! Since the single-lib unification the oaknode C ABI is gone; every query
|
||||
//! below operates directly on the oaknode Rust domain. A [`NodeRef`] pins a
|
||||
//! `NodeId` inside a project's graph (`Arc<Mutex<oaknode::project::Project>>`),
|
||||
//! replacing the opaque `CHandle` node/block/track references. Nullable
|
||||
//! references are plain `Option<NodeRef>` — the old `CHandle::null()`
|
||||
//! sentinel is `None`, and identity (`same_*`) is `NodeId` equality.
|
||||
//!
|
||||
//! Blocks/tracks that a command detaches from the project graph are stored
|
||||
//! as an owned [`oaknode::graph::NodeEntry`] on the command itself:
|
||||
//! [`block_remove_from_graph`] takes the entry out of the arena (identity is
|
||||
//! preserved for re-insertion) and [`block_add_to_graph`] puts it back — the
|
||||
//! Rust equivalent of the C++ `setParent(&memory_manager_)` re-homing.
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
|
||||
use oakcore_rs::Rational;
|
||||
use oaknode::block::{BlockCore, ClipBlockBehavior, GapBlockBehavior, TransitionBlockBehavior};
|
||||
use oaknode::graph::{Graph, NodeEntry};
|
||||
use oaknode::id::NodeId;
|
||||
use oaknode::node::NodeCore;
|
||||
use oaknode::project::Project;
|
||||
use oaknode::sequence::SequenceBehavior;
|
||||
use oaknode::track::{TrackBehavior, TrackListBehavior, TrackType};
|
||||
|
||||
use crate::bridge::node::{
|
||||
oaknode_block_get_in, oaknode_block_get_length, oaknode_block_get_next, oaknode_block_get_out,
|
||||
oaknode_block_get_previous, oaknode_block_get_track, oaknode_block_set_in,
|
||||
oaknode_block_set_length_and_media_in, oaknode_block_set_length_and_media_out,
|
||||
oaknode_node_get_project, oaknode_project_add_node, oaknode_project_remove_node,
|
||||
oaknode_sequence_as_node, oaknode_track_get_block_at, oaknode_track_get_block_count,
|
||||
oaknode_track_get_length, oaknode_track_get_sequence, oaknode_track_insert_block_after,
|
||||
oaknode_track_prepend_block,
|
||||
};
|
||||
use crate::handle::CHandle;
|
||||
/// A reference to a node — block, track, track list or sequence — inside
|
||||
/// a project's graph. `Clone` is cheap: the project `Arc` is shared and
|
||||
/// [`NodeId`] is `Copy`; the lifetime of the node follows the project.
|
||||
///
|
||||
/// This is the domain replacement for the opaque node/block/track
|
||||
/// `CHandle`s of the deleted oaknode C ABI.
|
||||
#[derive(Clone)]
|
||||
pub struct NodeRef {
|
||||
/// The owning project (its graph holds the node).
|
||||
pub project: Arc<Mutex<Project>>,
|
||||
/// The node's id in that project's graph.
|
||||
pub id: NodeId,
|
||||
}
|
||||
|
||||
impl NodeRef {
|
||||
/// New reference.
|
||||
pub fn new(project: Arc<Mutex<Project>>, id: NodeId) -> NodeRef {
|
||||
NodeRef { project, id }
|
||||
}
|
||||
|
||||
/// Lock the owning project. A poisoned lock is recovered so a
|
||||
/// panicking command body cannot wedge every later edit.
|
||||
fn lock(&self) -> MutexGuard<'_, Project> {
|
||||
self.project
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner())
|
||||
}
|
||||
}
|
||||
|
||||
/// Split a `Rational` into numerator/denominator pairs for the C ABI
|
||||
/// (timelineutil.h `rat_nd`).
|
||||
/// (timelineutil.h `rat_nd`). Kept for the ABI-compatible command paths.
|
||||
pub fn rat_nd(r: Rational, n: &mut i32, d: &mut i32) {
|
||||
*n = r.numerator() as i32;
|
||||
*d = r.denominator() as i32;
|
||||
}
|
||||
|
||||
/// Identity of two handles by `ctx` pointer equality.
|
||||
fn same_handle(a: CHandle, b: CHandle) -> bool {
|
||||
a.ctx == b.ctx
|
||||
/// `same_block`: two block references name the same graph node
|
||||
/// (`NodeId` equality; both are assumed to live in the same project).
|
||||
pub fn same_block(a: &NodeRef, b: &NodeRef) -> bool {
|
||||
a.id == b.id
|
||||
}
|
||||
|
||||
/// `same_block`: two block handles wrap the same object.
|
||||
pub fn same_block(a: CHandle, b: CHandle) -> bool {
|
||||
same_handle(a, b)
|
||||
/// `same_track`: two track references name the same graph node.
|
||||
pub fn same_track(a: &NodeRef, b: &NodeRef) -> bool {
|
||||
a.id == b.id
|
||||
}
|
||||
|
||||
/// `same_track`: two track handles wrap the same object.
|
||||
pub fn same_track(a: CHandle, b: CHandle) -> bool {
|
||||
same_handle(a, b)
|
||||
/// `same_node`: two node references name the same graph node.
|
||||
pub fn same_node(a: &NodeRef, b: &NodeRef) -> bool {
|
||||
a.id == b.id
|
||||
}
|
||||
|
||||
/// `same_node`: two node handles wrap the same object.
|
||||
pub fn same_node(a: CHandle, b: CHandle) -> bool {
|
||||
same_handle(a, b)
|
||||
/// Block kind (C++ `Block::Type` / the deleted `oaknode/block.h`
|
||||
/// `OAKNODE_BLOCK_*` values).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum BlockKind {
|
||||
/// Not a block.
|
||||
Other,
|
||||
/// Clip block.
|
||||
Clip,
|
||||
/// Gap block.
|
||||
Gap,
|
||||
/// Transition block.
|
||||
Transition,
|
||||
}
|
||||
|
||||
/// Total order over handles by `ctx`, so maps work across freshly boxed
|
||||
/// handles of the same origin.
|
||||
fn handle_less(a: CHandle, b: CHandle) -> Ordering {
|
||||
a.ctx.cmp(&b.ctx)
|
||||
// ---------------------------------------------------------------------------
|
||||
// Internal graph/behavior accessors
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Immutable [`BlockCore`] of the block node `id` (any block behavior
|
||||
/// kind shares the same core), or `None` for a stale/non-block node.
|
||||
fn block_core_of(project: &Project, id: NodeId) -> Option<&BlockCore> {
|
||||
let entry = project.graph.get(id)?;
|
||||
entry.behavior.as_any().and_then(|a| {
|
||||
if let Some(clip) = a.downcast_ref::<ClipBlockBehavior>() {
|
||||
Some(&clip.core)
|
||||
} else if let Some(gap) = a.downcast_ref::<GapBlockBehavior>() {
|
||||
Some(&gap.core)
|
||||
} else if let Some(transition) = a.downcast_ref::<TransitionBlockBehavior>() {
|
||||
Some(&transition.core)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// `BlockHandleLess`: total order over block handles by wrapped native
|
||||
/// pointer (identity), so maps work across freshly-boxed borrowed handles.
|
||||
pub fn block_handle_less(a: CHandle, b: CHandle) -> std::cmp::Ordering {
|
||||
handle_less(a, b)
|
||||
}
|
||||
|
||||
/// `TrackHandleLess`: total order over track handles by wrapped native
|
||||
/// pointer.
|
||||
pub fn track_handle_less(a: CHandle, b: CHandle) -> std::cmp::Ordering {
|
||||
handle_less(a, b)
|
||||
}
|
||||
|
||||
/// `free_detached_handle`: re-take ownership of a detached object's handle
|
||||
/// then release, destroying it (timelineutil.h).
|
||||
///
|
||||
/// In this crate's model a detached block/track handle is an owned handle
|
||||
/// (it owns its box), so releasing it destroys the object. NULL/empty no-op.
|
||||
pub fn free_detached_handle(h: *mut CHandle) {
|
||||
if h.is_null() {
|
||||
return;
|
||||
/// Mutable [`BlockCore`] of the block node `id`, or `None`.
|
||||
fn block_core_of_mut(project: &mut Project, id: NodeId) -> Option<&mut BlockCore> {
|
||||
let entry = project.graph.get_mut(id)?;
|
||||
let a = entry.behavior.as_any_mut()?;
|
||||
if a.is::<ClipBlockBehavior>() {
|
||||
Some(&mut a.downcast_mut::<ClipBlockBehavior>().expect("checked above").core)
|
||||
} else if a.is::<GapBlockBehavior>() {
|
||||
Some(&mut a.downcast_mut::<GapBlockBehavior>().expect("checked above").core)
|
||||
} else if a.is::<TransitionBlockBehavior>() {
|
||||
Some(
|
||||
&mut a
|
||||
.downcast_mut::<TransitionBlockBehavior>()
|
||||
.expect("checked above")
|
||||
.core,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
// SAFETY: caller passes a valid handle pointer.
|
||||
let handle = unsafe { &mut *h };
|
||||
if handle.ctx.is_null() {
|
||||
return;
|
||||
}
|
||||
if let Some(release) = handle.release {
|
||||
// SAFETY: `release` is the boxed type's release callback.
|
||||
unsafe { release(handle.ctx) };
|
||||
}
|
||||
handle.ctx = std::ptr::null_mut();
|
||||
handle.addref = None;
|
||||
handle.release = None;
|
||||
handle.abi_version = 0;
|
||||
}
|
||||
|
||||
/// Read a block time field as a `Rational`.
|
||||
fn block_pair(b: CHandle, f: fn(CHandle, *mut i32, *mut i32) -> i32) -> Rational {
|
||||
let mut n = 0;
|
||||
let mut d = 0;
|
||||
// SAFETY: `n`/`d` are valid out pointers.
|
||||
let _ = unsafe { f(b, &mut n, &mut d) };
|
||||
Rational::new(n as i64, d as i64)
|
||||
/// Immutable [`TrackBehavior`] of the track node `id`, or `None`.
|
||||
fn track_behavior_of(project: &Project, id: NodeId) -> Option<&TrackBehavior> {
|
||||
project.graph.get(id).and_then(|e| {
|
||||
e.behavior
|
||||
.as_any()
|
||||
.and_then(|a| a.downcast_ref::<TrackBehavior>())
|
||||
})
|
||||
}
|
||||
|
||||
/// `block_in`: in point as a `Rational`.
|
||||
pub fn block_in(b: CHandle) -> Rational {
|
||||
block_pair(b, oaknode_block_get_in)
|
||||
/// Mutable [`TrackBehavior`] of the track node `id`, or `None`.
|
||||
fn track_behavior_of_mut(project: &mut Project, id: NodeId) -> Option<&mut TrackBehavior> {
|
||||
project.graph.get_mut(id).and_then(|e| {
|
||||
e.behavior
|
||||
.as_any_mut()
|
||||
.and_then(|a| a.downcast_mut::<TrackBehavior>())
|
||||
})
|
||||
}
|
||||
|
||||
/// Immutable [`TrackListBehavior`] of the track-list node `id`, or `None`.
|
||||
fn tracklist_behavior_of(project: &Project, id: NodeId) -> Option<&TrackListBehavior> {
|
||||
project.graph.get(id).and_then(|e| {
|
||||
e.behavior
|
||||
.as_any()
|
||||
.and_then(|a| a.downcast_ref::<TrackListBehavior>())
|
||||
})
|
||||
}
|
||||
|
||||
/// Mutable [`TrackListBehavior`] of the track-list node `id`, or `None`.
|
||||
fn tracklist_behavior_of_mut(project: &mut Project, id: NodeId) -> Option<&mut TrackListBehavior> {
|
||||
project.graph.get_mut(id).and_then(|e| {
|
||||
e.behavior
|
||||
.as_any_mut()
|
||||
.and_then(|a| a.downcast_mut::<TrackListBehavior>())
|
||||
})
|
||||
}
|
||||
|
||||
/// Immutable [`SequenceBehavior`] of the sequence node `id`, or `None`.
|
||||
fn sequence_behavior_of(project: &Project, id: NodeId) -> Option<&SequenceBehavior> {
|
||||
project.graph.get(id).and_then(|e| {
|
||||
e.behavior
|
||||
.as_any()
|
||||
.and_then(|a| a.downcast_ref::<SequenceBehavior>())
|
||||
})
|
||||
}
|
||||
|
||||
/// Graph-backed [`oaknode::track::BlockRange`] view: the block span
|
||||
/// queries the track needs (in/out), read from the `BlockCore`s in the
|
||||
/// arena.
|
||||
pub struct GraphBlockRange<'a> {
|
||||
/// The graph the block nodes live in.
|
||||
pub graph: &'a Graph,
|
||||
}
|
||||
|
||||
impl oaknode::track::BlockRange for GraphBlockRange<'_> {
|
||||
fn in_(&self, block: NodeId) -> Rational {
|
||||
self.graph
|
||||
.get(block)
|
||||
.and_then(|e| e.behavior.as_any())
|
||||
.and_then(|a| {
|
||||
[
|
||||
a.downcast_ref::<ClipBlockBehavior>()
|
||||
.map(|b| &b.core),
|
||||
a.downcast_ref::<GapBlockBehavior>().map(|b| &b.core),
|
||||
a.downcast_ref::<TransitionBlockBehavior>()
|
||||
.map(|b| &b.core),
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.next()
|
||||
})
|
||||
.map(BlockCore::in_)
|
||||
.unwrap_or_else(|| Rational::new(0, 1))
|
||||
}
|
||||
|
||||
fn out(&self, block: NodeId) -> Rational {
|
||||
self.graph
|
||||
.get(block)
|
||||
.and_then(|e| e.behavior.as_any())
|
||||
.and_then(|a| {
|
||||
[
|
||||
a.downcast_ref::<ClipBlockBehavior>()
|
||||
.map(|b| &b.core),
|
||||
a.downcast_ref::<GapBlockBehavior>().map(|b| &b.core),
|
||||
a.downcast_ref::<TransitionBlockBehavior>()
|
||||
.map(|b| &b.core),
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.next()
|
||||
})
|
||||
.map(BlockCore::out)
|
||||
.unwrap_or_else(|| Rational::new(0, 1))
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Block value queries / mutations
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// `block_in`: in point as a `Rational` (0/1 for a stale/non-block node,
|
||||
/// matching the deleted bridge's untouched-output failure path).
|
||||
pub fn block_in(b: &NodeRef) -> Rational {
|
||||
let p = b.lock();
|
||||
block_core_of(&p, b.id).map(BlockCore::in_).unwrap_or_else(|| Rational::new(0, 1))
|
||||
}
|
||||
|
||||
/// `block_out`: out point as a `Rational`.
|
||||
pub fn block_out(b: CHandle) -> Rational {
|
||||
block_pair(b, oaknode_block_get_out)
|
||||
pub fn block_out(b: &NodeRef) -> Rational {
|
||||
let p = b.lock();
|
||||
block_core_of(&p, b.id).map(BlockCore::out).unwrap_or_else(|| Rational::new(0, 1))
|
||||
}
|
||||
|
||||
/// `block_length`: block length as a `Rational`.
|
||||
pub fn block_length(b: CHandle) -> Rational {
|
||||
block_pair(b, oaknode_block_get_length)
|
||||
pub fn block_length(b: &NodeRef) -> Rational {
|
||||
let p = b.lock();
|
||||
block_core_of(&p, b.id)
|
||||
.map(BlockCore::length)
|
||||
.unwrap_or_else(|| Rational::new(0, 1))
|
||||
}
|
||||
|
||||
/// `block_set_length_and_media_out`.
|
||||
pub fn block_set_length_and_media_out(b: CHandle, len: Rational) {
|
||||
let (mut n, mut d) = (0, 0);
|
||||
rat_nd(len, &mut n, &mut d);
|
||||
// SAFETY: `n`/`d` are valid ints.
|
||||
let _ = unsafe { oaknode_block_set_length_and_media_out(b, n, d) };
|
||||
/// `block_kind`: which block behavior backs the node (C++
|
||||
/// `oaknode_block_get_kind`).
|
||||
pub fn block_kind(b: &NodeRef) -> BlockKind {
|
||||
let p = b.lock();
|
||||
match p.graph.get(b.id).and_then(|e| e.behavior.as_any()) {
|
||||
Some(a) if a.downcast_ref::<ClipBlockBehavior>().is_some() => BlockKind::Clip,
|
||||
Some(a) if a.downcast_ref::<GapBlockBehavior>().is_some() => BlockKind::Gap,
|
||||
Some(a) if a.downcast_ref::<TransitionBlockBehavior>().is_some() => BlockKind::Transition,
|
||||
_ => BlockKind::Other,
|
||||
}
|
||||
}
|
||||
|
||||
/// `block_set_length_and_media_in`.
|
||||
pub fn block_set_length_and_media_in(b: CHandle, len: Rational) {
|
||||
let (mut n, mut d) = (0, 0);
|
||||
rat_nd(len, &mut n, &mut d);
|
||||
// SAFETY: `n`/`d` are valid ints.
|
||||
let _ = unsafe { oaknode_block_set_length_and_media_in(b, n, d) };
|
||||
/// `oaknode_block_get_enabled`: the block's enabled flag (false for a
|
||||
/// stale node).
|
||||
pub fn block_enabled(b: &NodeRef) -> bool {
|
||||
let p = b.lock();
|
||||
block_core_of(&p, b.id).map(|c| c.enabled).unwrap_or(false)
|
||||
}
|
||||
|
||||
/// `oaknode_block_set_enabled`.
|
||||
pub fn block_set_enabled(b: &NodeRef, enabled: bool) {
|
||||
let mut p = b.lock();
|
||||
if let Some(core) = block_core_of_mut(&mut p, b.id) {
|
||||
core.enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
/// `block_set_length_and_media_out`: keep the out point anchored (the
|
||||
/// in point shifts).
|
||||
pub fn block_set_length_and_media_out(b: &NodeRef, len: Rational) {
|
||||
let mut p = b.lock();
|
||||
if let Some(core) = block_core_of_mut(&mut p, b.id) {
|
||||
core.set_length_and_media_out(len);
|
||||
}
|
||||
}
|
||||
|
||||
/// `block_set_length_and_media_in`: keep the in point anchored (the
|
||||
/// out point shifts).
|
||||
pub fn block_set_length_and_media_in(b: &NodeRef, len: Rational) {
|
||||
let mut p = b.lock();
|
||||
if let Some(core) = block_core_of_mut(&mut p, b.id) {
|
||||
core.set_length_and_media_in(len);
|
||||
}
|
||||
}
|
||||
|
||||
/// `block_set_in`: set the in point, keeping the length (the out follows).
|
||||
pub fn block_set_in(b: CHandle, in_: Rational) {
|
||||
let (mut n, mut d) = (0, 0);
|
||||
rat_nd(in_, &mut n, &mut d);
|
||||
// SAFETY: `n`/`d` are valid ints.
|
||||
let _ = unsafe { oaknode_block_set_in(b, n, d) };
|
||||
}
|
||||
|
||||
/// `track_length`: track length as a `Rational`.
|
||||
pub fn track_length(t: CHandle) -> Rational {
|
||||
let mut n = 0;
|
||||
let mut d = 0;
|
||||
// SAFETY: `n`/`d` are valid out pointers.
|
||||
let _ = unsafe { oaknode_track_get_length(t, &mut n, &mut d) };
|
||||
Rational::new(n as i64, d as i64)
|
||||
}
|
||||
|
||||
/// `block_previous`: the block before `b` on its track.
|
||||
pub fn block_previous(b: CHandle) -> CHandle {
|
||||
let mut out = CHandle::null();
|
||||
// SAFETY: `out` is a valid out pointer.
|
||||
let _ = unsafe { oaknode_block_get_previous(b, &mut out) };
|
||||
out
|
||||
}
|
||||
|
||||
/// `block_next`: the block after `b` on its track.
|
||||
pub fn block_next(b: CHandle) -> CHandle {
|
||||
let mut out = CHandle::null();
|
||||
// SAFETY: `out` is a valid out pointer.
|
||||
let _ = unsafe { oaknode_block_get_next(b, &mut out) };
|
||||
out
|
||||
}
|
||||
|
||||
/// `block_track`: the track owning `b`.
|
||||
pub fn block_track(b: CHandle) -> CHandle {
|
||||
let mut out = CHandle::null();
|
||||
// SAFETY: `out` is a valid out pointer.
|
||||
let _ = unsafe { oaknode_block_get_track(b, &mut out) };
|
||||
out
|
||||
}
|
||||
|
||||
/// `track_project`: the project graph owning the track's sequence, via
|
||||
/// `oaknode_track_get_sequence` + `oaknode_sequence_as_node` +
|
||||
/// `oaknode_node_get_project`.
|
||||
pub fn track_project(track: CHandle) -> CHandle {
|
||||
let mut sequence = CHandle::null();
|
||||
if unsafe { oaknode_track_get_sequence(track, &mut sequence) } != 0 || sequence.is_null() {
|
||||
return CHandle::null();
|
||||
}
|
||||
let mut project = CHandle::null();
|
||||
// SAFETY: `project` is a valid out pointer; the sequence node view is valid.
|
||||
let _ = unsafe { oaknode_node_get_project(oaknode_sequence_as_node(sequence), &mut project) };
|
||||
project
|
||||
}
|
||||
|
||||
/// C++ `oaknode_track_append_block` — not exposed by the bridge, so append is
|
||||
/// synthesized as insert-after the last block (prepend on an empty track).
|
||||
pub fn track_append_block(track: CHandle, block: CHandle) {
|
||||
let mut count = 0;
|
||||
// SAFETY: `count` is a valid out pointer.
|
||||
let _ = unsafe { oaknode_track_get_block_count(track.clone(), &mut count) };
|
||||
if count > 0 {
|
||||
let mut last = CHandle::null();
|
||||
// SAFETY: `last` is a valid out pointer.
|
||||
let _ = unsafe { oaknode_track_get_block_at(track.clone(), count - 1, &mut last) };
|
||||
// SAFETY: `track`/`last` are valid handles.
|
||||
let _ = unsafe { oaknode_track_insert_block_after(track, block, last) };
|
||||
} else {
|
||||
// SAFETY: valid handles.
|
||||
let _ = unsafe { oaknode_track_prepend_block(track, block) };
|
||||
pub fn block_set_in(b: &NodeRef, in_: Rational) {
|
||||
let mut p = b.lock();
|
||||
if let Some(core) = block_core_of_mut(&mut p, b.id) {
|
||||
core.set_in(in_);
|
||||
}
|
||||
}
|
||||
|
||||
/// C++ `oaknode_track_insert_block_before` — not exposed by the bridge, so
|
||||
/// insert-before is synthesized as insert-after `next`'s predecessor (prepend
|
||||
/// when `next` is the first block on the track).
|
||||
pub fn track_insert_block_before(track: CHandle, block: CHandle, next: CHandle) {
|
||||
let prev = block_previous(next.clone());
|
||||
if prev.is_null() {
|
||||
// SAFETY: valid handles.
|
||||
let _ = unsafe { oaknode_track_prepend_block(track, block) };
|
||||
} else {
|
||||
// SAFETY: valid handles.
|
||||
let _ = unsafe { oaknode_track_insert_block_after(track, block, prev) };
|
||||
/// `oaknode_clip_get_media_in`: the block's media-in point. The media
|
||||
/// fields live on the shared [`BlockCore`], so this works for any block
|
||||
/// kind (the deleted ABI only exposed it for clips; closest behavior).
|
||||
pub fn clip_media_in(b: &NodeRef) -> Rational {
|
||||
let p = b.lock();
|
||||
block_core_of(&p, b.id)
|
||||
.map(|c| c.media_in)
|
||||
.unwrap_or_else(|| Rational::new(0, 1))
|
||||
}
|
||||
|
||||
/// `oaknode_clip_set_media_in`.
|
||||
pub fn clip_set_media_in(b: &NodeRef, media_in: Rational) {
|
||||
let mut p = b.lock();
|
||||
if let Some(core) = block_core_of_mut(&mut p, b.id) {
|
||||
core.media_in = media_in;
|
||||
}
|
||||
}
|
||||
|
||||
/// `block_add_to_graph`: attach `b` to the project graph owning `track`.
|
||||
// ---------------------------------------------------------------------------
|
||||
// Block navigation (track membership)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// `block_previous`: the block before `b` on its track (`None` when
|
||||
/// trackless or first).
|
||||
pub fn block_previous(b: &NodeRef) -> Option<NodeRef> {
|
||||
let p = b.lock();
|
||||
let track_id = block_core_of(&p, b.id).and_then(|c| c.track)?;
|
||||
let blocks = &track_behavior_of(&p, track_id)?.blocks;
|
||||
let index = blocks.iter().position(|id| *id == b.id)?;
|
||||
if index == 0 {
|
||||
return None;
|
||||
}
|
||||
Some(NodeRef::new(b.project.clone(), blocks[index - 1]))
|
||||
}
|
||||
|
||||
/// `block_next`: the block after `b` on its track (`None` when trackless
|
||||
/// or last).
|
||||
pub fn block_next(b: &NodeRef) -> Option<NodeRef> {
|
||||
let p = b.lock();
|
||||
let track_id = block_core_of(&p, b.id).and_then(|c| c.track)?;
|
||||
let blocks = &track_behavior_of(&p, track_id)?.blocks;
|
||||
let index = blocks.iter().position(|id| *id == b.id)?;
|
||||
blocks
|
||||
.get(index + 1)
|
||||
.map(|id| NodeRef::new(b.project.clone(), *id))
|
||||
}
|
||||
|
||||
/// `block_track`: the track owning `b` (`None` when detached from its
|
||||
/// track, e.g. after a ripple-remove).
|
||||
pub fn block_track(b: &NodeRef) -> Option<NodeRef> {
|
||||
let p = b.lock();
|
||||
block_core_of(&p, b.id)
|
||||
.and_then(|c| c.track)
|
||||
.map(|id| NodeRef::new(b.project.clone(), id))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Track queries / mutations
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// `track_length`: track length as a `Rational` (end of the last block).
|
||||
pub fn track_length(t: &NodeRef) -> Rational {
|
||||
let p = t.lock();
|
||||
match track_behavior_of(&p, t.id) {
|
||||
Some(track) => track.length(&GraphBlockRange { graph: &p.graph }),
|
||||
None => Rational::new(0, 1),
|
||||
}
|
||||
}
|
||||
|
||||
/// `oaknode_track_get_block_count`.
|
||||
pub fn track_block_count(t: &NodeRef) -> usize {
|
||||
let p = t.lock();
|
||||
track_behavior_of(&p, t.id).map(|t| t.blocks.len()).unwrap_or(0)
|
||||
}
|
||||
|
||||
/// `oaknode_track_get_block_at`: block at `index` on the track.
|
||||
pub fn track_block_at(t: &NodeRef, index: usize) -> Option<NodeRef> {
|
||||
let p = t.lock();
|
||||
track_behavior_of(&p, t.id)
|
||||
.and_then(|t| t.blocks.get(index))
|
||||
.map(|id| NodeRef::new(t.project.clone(), *id))
|
||||
}
|
||||
|
||||
/// `oaknode_track_get_locked`.
|
||||
pub fn track_locked(t: &NodeRef) -> bool {
|
||||
let p = t.lock();
|
||||
track_behavior_of(&p, t.id).map(|t| t.locked).unwrap_or(false)
|
||||
}
|
||||
|
||||
/// `oaknode_track_get_nearest_block_before_or_at`: the block containing
|
||||
/// `time`, else the last block entirely before it. `None` when the track
|
||||
/// is empty or `time` precedes the first block.
|
||||
///
|
||||
/// The block handle is passed through directly (not `oaknode_block_as_node`):
|
||||
/// `oaknode_project_add_node` rewrites the shared node box it receives
|
||||
/// (`write_node_ref`) to the new project, so later track operations on `b`
|
||||
/// (which adopt blocks by comparing the handle's project) see the up-to-date
|
||||
/// ownership instead of faulting with `E_NOT_FOUND` on a stale scratch id.
|
||||
pub fn block_add_to_graph(b: CHandle, track: CHandle) {
|
||||
let project = track_project(track);
|
||||
if !project.is_null() {
|
||||
// SAFETY: `project` is a valid handle; `b` is a node/block handle.
|
||||
let _ = unsafe { oaknode_project_add_node(project, b) };
|
||||
/// CPP-PARITY timelineundoripple.cpp:64.
|
||||
pub fn track_nearest_block_before_or_at(t: &NodeRef, time: Rational) -> Option<NodeRef> {
|
||||
let p = t.lock();
|
||||
let blocks = track_behavior_of(&p, t.id)?.blocks.clone();
|
||||
let mut before: Option<NodeId> = None;
|
||||
for id in blocks {
|
||||
let core = block_core_of(&p, id)?;
|
||||
if core.in_() <= time && core.out() > time {
|
||||
return Some(NodeRef::new(t.project.clone(), id));
|
||||
}
|
||||
if core.out() <= time {
|
||||
before = Some(id);
|
||||
}
|
||||
}
|
||||
before.map(|id| NodeRef::new(t.project.clone(), id))
|
||||
}
|
||||
|
||||
/// `oaknode_track_get_nearest_block_after_or_at`: the block containing
|
||||
/// `time`, else the first block starting at/after it.
|
||||
///
|
||||
/// CPP-PARITY timelineundoripple.cpp:551.
|
||||
pub fn track_nearest_block_after_or_at(t: &NodeRef, time: Rational) -> Option<NodeRef> {
|
||||
let p = t.lock();
|
||||
let blocks = track_behavior_of(&p, t.id)?.blocks.clone();
|
||||
let mut after: Option<NodeId> = None;
|
||||
for id in blocks {
|
||||
let core = block_core_of(&p, id)?;
|
||||
if core.in_() <= time && core.out() > time {
|
||||
return Some(NodeRef::new(t.project.clone(), id));
|
||||
}
|
||||
if core.in_() >= time {
|
||||
after = Some(id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
after.map(|id| NodeRef::new(t.project.clone(), id))
|
||||
}
|
||||
|
||||
/// C++ `oaknode_track_append_block` — append a block at the end of the
|
||||
/// track's block list.
|
||||
pub fn track_append_block(track: &NodeRef, block: &NodeRef) {
|
||||
let mut p = track.lock();
|
||||
let ok = track_behavior_of_mut(&mut p, track.id)
|
||||
.map(|t| {
|
||||
t.append_block(block.id);
|
||||
true
|
||||
})
|
||||
.unwrap_or(false);
|
||||
if ok {
|
||||
if let Some(core) = block_core_of_mut(&mut p, block.id) {
|
||||
core.track = Some(track.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `block_remove_from_graph`: detach `b` from the project graph owning
|
||||
/// `track`.
|
||||
///
|
||||
/// Like [`block_add_to_graph`], `b` is passed through so `write_node_ref`
|
||||
/// re-homes the shared handle into the scratch project (the caller's handle
|
||||
/// becomes the owner again, matching the C++ `setParent(&memory_manager_)`).
|
||||
pub fn block_remove_from_graph(b: CHandle, track: CHandle) {
|
||||
let project = track_project(track);
|
||||
if !project.is_null() {
|
||||
// SAFETY: `project` is a valid handle; `b` is a node/block handle.
|
||||
let _ = unsafe { oaknode_project_remove_node(project, b) };
|
||||
/// C++ `oaknode_track_insert_block_before`: insert `block` before `next`
|
||||
/// on the track (prepend when `next` is absent).
|
||||
pub fn track_insert_block_before(track: &NodeRef, block: &NodeRef, next: &NodeRef) {
|
||||
let mut p = track.lock();
|
||||
let ok = track_behavior_of_mut(&mut p, track.id)
|
||||
.map(|t| t.insert_block_before(block.id, next.id))
|
||||
.unwrap_or(false);
|
||||
if ok {
|
||||
if let Some(core) = block_core_of_mut(&mut p, block.id) {
|
||||
core.track = Some(track.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// C++ `oaknode_track_insert_block_after`: insert `block` after `before`
|
||||
/// on the track. `None` for `before` prepends (the deleted ABI tolerated
|
||||
/// an empty predecessor like C++ did).
|
||||
pub fn track_insert_block_after(track: &NodeRef, block: &NodeRef, before: Option<&NodeRef>) {
|
||||
let mut p = track.lock();
|
||||
let ok = track_behavior_of_mut(&mut p, track.id)
|
||||
.map(|t| match before {
|
||||
Some(b) => t.insert_block_after(block.id, b.id),
|
||||
None => {
|
||||
t.prepend_block(block.id);
|
||||
true
|
||||
}
|
||||
})
|
||||
.unwrap_or(false);
|
||||
if ok {
|
||||
if let Some(core) = block_core_of_mut(&mut p, block.id) {
|
||||
core.track = Some(track.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// C++ `oaknode_track_prepend_block`.
|
||||
pub fn track_prepend_block(track: &NodeRef, block: &NodeRef) {
|
||||
let mut p = track.lock();
|
||||
let ok = track_behavior_of_mut(&mut p, track.id)
|
||||
.map(|t| {
|
||||
t.prepend_block(block.id);
|
||||
true
|
||||
})
|
||||
.unwrap_or(false);
|
||||
if ok {
|
||||
if let Some(core) = block_core_of_mut(&mut p, block.id) {
|
||||
core.track = Some(track.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// C++ `oaknode_track_replace_block`: swap `old` for `replace` at the
|
||||
/// same track position. `old` loses its track membership, `replace`
|
||||
/// gains it.
|
||||
pub fn track_replace_block(track: &NodeRef, old: &NodeRef, replace: &NodeRef) {
|
||||
let mut p = track.lock();
|
||||
let ok = track_behavior_of_mut(&mut p, track.id)
|
||||
.map(|t| t.replace_block(old.id, replace.id))
|
||||
.unwrap_or(false);
|
||||
if ok {
|
||||
if let Some(core) = block_core_of_mut(&mut p, old.id) {
|
||||
core.track = None;
|
||||
}
|
||||
if let Some(core) = block_core_of_mut(&mut p, replace.id) {
|
||||
core.track = Some(track.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// C++ `oaknode_track_ripple_remove_block`: remove the block from its
|
||||
/// track. In the module model the block's in/out points are stored on
|
||||
/// the block (not derived from track order), so "ripple" is only the
|
||||
/// membership removal — positions of later blocks stay as stored, which
|
||||
/// the undo re-insertion mirrors exactly (CPP-PARITY deviation).
|
||||
pub fn track_ripple_remove_block(track: &NodeRef, block: &NodeRef) {
|
||||
let mut p = track.lock();
|
||||
let ok = track_behavior_of_mut(&mut p, track.id)
|
||||
.map(|t| t.ripple_remove_block(block.id))
|
||||
.unwrap_or(false);
|
||||
if ok {
|
||||
if let Some(core) = block_core_of_mut(&mut p, block.id) {
|
||||
core.track = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Project graph attach / detach (C++ `setParent(&memory_manager_)` paths)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// `block_add_to_graph`: re-insert a previously detached block entry
|
||||
/// into the project graph, preserving its identity where the original
|
||||
/// slot is free (C++ re-parenting from the scratch memory manager back
|
||||
/// into the project). Passing `None` is a no-op — blocks created via
|
||||
/// [`block_gap_create`]/[`block_clip_create`] are already in the graph.
|
||||
pub fn block_add_to_graph(block: &NodeRef, entry: Option<NodeEntry>) {
|
||||
if let Some(entry) = entry {
|
||||
let mut p = block.lock();
|
||||
p.graph.add_entry(entry, block.id);
|
||||
}
|
||||
}
|
||||
|
||||
/// `block_remove_from_graph`: detach `block` from the project graph,
|
||||
/// returning its full entry for the caller to own (and re-add with
|
||||
/// [`block_add_to_graph`]). `None` when the node is stale or already
|
||||
/// detached.
|
||||
pub fn block_remove_from_graph(block: &NodeRef) -> Option<NodeEntry> {
|
||||
let mut p = block.lock();
|
||||
p.graph.take_node(block.id)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Block / track / track-list creation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// C++ `oaknode_block_clip_create`: a fresh clip block node in the given
|
||||
/// project's graph.
|
||||
pub fn block_clip_create(project: &Arc<Mutex<Project>>) -> NodeRef {
|
||||
let (core, behavior) = oaknode::block::clip_create();
|
||||
let id = {
|
||||
let mut p = project
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
p.graph.add_node(core, behavior)
|
||||
};
|
||||
NodeRef::new(project.clone(), id)
|
||||
}
|
||||
|
||||
/// C++ `oaknode_block_gap_create`: a fresh gap block node in the given
|
||||
/// project's graph.
|
||||
pub fn block_gap_create(project: &Arc<Mutex<Project>>) -> NodeRef {
|
||||
let (core, behavior) = oaknode::block::gap_create();
|
||||
let id = {
|
||||
let mut p = project
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
p.graph.add_node(core, behavior)
|
||||
};
|
||||
NodeRef::new(project.clone(), id)
|
||||
}
|
||||
|
||||
/// C++ `oaknode_track_create`: a fresh track node of the given type in
|
||||
/// the project's graph (detached from any track list until appended).
|
||||
pub fn track_create(project: &Arc<Mutex<Project>>, kind: TrackType) -> NodeRef {
|
||||
let core = NodeCore::new();
|
||||
let behavior = Box::new(TrackBehavior::new(kind));
|
||||
let id = {
|
||||
let mut p = project
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
p.graph.add_node(core, behavior)
|
||||
};
|
||||
NodeRef::new(project.clone(), id)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Track list / sequence queries
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// `oaknode_tracklist_get_track_count`.
|
||||
pub fn tracklist_track_count(list: &NodeRef) -> usize {
|
||||
let p = list.lock();
|
||||
tracklist_behavior_of(&p, list.id)
|
||||
.map(|l| l.tracks.len())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
/// `oaknode_tracklist_get_track_at`: track at `index` in the list.
|
||||
pub fn tracklist_track_at(list: &NodeRef, index: usize) -> Option<NodeRef> {
|
||||
let p = list.lock();
|
||||
tracklist_behavior_of(&p, list.id)
|
||||
.and_then(|l| l.tracks.get(index))
|
||||
.map(|id| NodeRef::new(list.project.clone(), *id))
|
||||
}
|
||||
|
||||
/// `oaknode_tracklist_get_type`: the list's media type (`None` for a
|
||||
/// stale node).
|
||||
pub fn tracklist_type(list: &NodeRef) -> Option<TrackType> {
|
||||
let p = list.lock();
|
||||
tracklist_behavior_of(&p, list.id).map(|l| l.kind)
|
||||
}
|
||||
|
||||
/// `oaknode_tracklist_array_append`: append `track` to the list (sets the
|
||||
/// track's list membership and its index).
|
||||
pub fn tracklist_append(list: &NodeRef, track: &NodeRef) {
|
||||
let mut p = list.lock();
|
||||
let index = tracklist_behavior_of_mut(&mut p, list.id)
|
||||
.map(|l| {
|
||||
l.tracks.push(track.id);
|
||||
l.tracks.len() as i32 - 1
|
||||
})
|
||||
.unwrap_or(-1);
|
||||
if index >= 0 {
|
||||
if let Some(t) = track_behavior_of_mut(&mut p, track.id) {
|
||||
t.track_list = Some(list.id);
|
||||
t.index = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `oaknode_tracklist_array_remove_last`: drop the last track from the
|
||||
/// list (detaching its list membership; the node itself is removed from
|
||||
/// the graph separately by the caller).
|
||||
pub fn tracklist_remove_last(list: &NodeRef) -> Option<NodeRef> {
|
||||
let mut p = list.lock();
|
||||
let removed = tracklist_behavior_of_mut(&mut p, list.id)
|
||||
.and_then(|l| l.tracks.pop())?;
|
||||
if let Some(t) = track_behavior_of_mut(&mut p, removed) {
|
||||
t.track_list = None;
|
||||
t.index = 0;
|
||||
}
|
||||
Some(NodeRef::new(list.project.clone(), removed))
|
||||
}
|
||||
|
||||
/// `oaknode_sequence_get_track_list`: the sequence's track list of the
|
||||
/// given type.
|
||||
pub fn sequence_track_list(sequence: &NodeRef, kind: TrackType) -> Option<NodeRef> {
|
||||
let p = sequence.lock();
|
||||
let lists = sequence_behavior_of(&p, sequence.id)?.track_lists.clone();
|
||||
for id in lists {
|
||||
if tracklist_behavior_of(&p, id).map(|l| l.kind) == Some(kind) {
|
||||
return Some(NodeRef::new(sequence.project.clone(), id));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// `oaknode_sequence_get_all_track_count` / `get_all_track_at`: every
|
||||
/// track of every track list owned by the sequence, in list order.
|
||||
pub fn sequence_all_tracks(sequence: &NodeRef) -> Vec<NodeRef> {
|
||||
let p = sequence.lock();
|
||||
let mut tracks = Vec::new();
|
||||
if let Some(seq) = sequence_behavior_of(&p, sequence.id) {
|
||||
let lists = seq.track_lists.clone();
|
||||
for list_id in lists {
|
||||
if let Some(list) = tracklist_behavior_of(&p, list_id) {
|
||||
for track_id in &list.tracks {
|
||||
tracks.push(NodeRef::new(sequence.project.clone(), *track_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tracks
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
//! job.
|
||||
|
||||
use oakcore_rs::{Rational, TimeRange};
|
||||
use oakundo::undocommand::UndoCommand;
|
||||
|
||||
/// The reset sentinel `k_reset_in` (timelineworkarea.h): 0/1. Exposed as a
|
||||
/// function because `Rational::new` is not yet `const` in oakcore-rs.
|
||||
@@ -125,7 +126,7 @@ impl WorkareaSetEnabledCommand {
|
||||
}
|
||||
|
||||
/// Wrap as an oakundo vtable command handle.
|
||||
pub fn to_command(self) -> crate::handle::CHandle {
|
||||
pub fn to_command(self) -> UndoCommand {
|
||||
crate::undocommon::box_command(self)
|
||||
}
|
||||
}
|
||||
@@ -163,8 +164,8 @@ impl WorkareaSetRangeCommand {
|
||||
}
|
||||
|
||||
/// Construct from work area + new range + explicitly supplied old
|
||||
/// range (used by the FFI layer, which may have a previously captured
|
||||
/// range; `new` delegates here with the current range).
|
||||
/// range (used by callers that may have a previously captured range;
|
||||
/// `new` delegates here with the current range).
|
||||
pub fn new_with_old(
|
||||
workarea: crate::handle::CHandle,
|
||||
range: TimeRange,
|
||||
@@ -192,7 +193,7 @@ impl WorkareaSetRangeCommand {
|
||||
}
|
||||
|
||||
/// Wrap as an oakundo vtable command handle.
|
||||
pub fn to_command(self) -> crate::handle::CHandle {
|
||||
pub fn to_command(self) -> UndoCommand {
|
||||
crate::undocommon::box_command(self)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user