CI / Build & test (Windows) (push) Failing after 7s
All crates take the oak-* kebab-case naming (oak-audio, oak-codec, oak-common, oak-core, oak-ffmpeg-link, oak-node, oak-otio, oak-plugin, oak-render, oak-storage, oak-task, oak-timeline, oak-undo), with the lib identifiers rewritten (oakrender:: -> oak_render::, oakcore_rs:: -> oak_core::, ...) across all 226 referencing files. The GUI application moves from the workspace root into crates/oak-app/: src/, build.rs (paths fixed for the new location) and tests/ travel with it, the root Cargo.toml becomes workspace-only ([workspace] + workspace.package + profiles), and the app package inherits the workspace version. The screenshots example becomes a standalone crate examples/simple_player/ with its own Cargo.toml. Every crate now inherits the single workspace version (version.workspace = true), and the workflows' crate paths and the build docs follow the renames. Validated with a clean cargo check --workspace.
705 lines
19 KiB
Rust
705 lines
19 KiB
Rust
// Oak Video Editor - Non-Linear Video Editor
|
|
// Copyright (C) 2026 Oak Team
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
//! Timeline markers and the marker undo-command family
|
|
//! (`src/timeline/src/timelinemarker.h`): `TimelineMarker`/`TimelineMarkerList`
|
|
//! plus `MarkerAdd`/`MarkerRemove`/`MarkerChangeColor`/`MarkerChangeName`/
|
|
//! `MarkerChangeTime` commands.
|
|
//!
|
|
//! The list keeps markers sorted by time (`// CPP-PARITY:
|
|
//! src/timeline/src/timelinemarker.h` `insert_into_list`). De-Qt: no QObject,
|
|
//! no signals — change notifications are the facade's job. `set_time` does
|
|
//! not re-sort (the De-Qt marker has no parent pointer); callers restore
|
|
//! order via `TimelineMarkerList::resort`/`resort_at`.
|
|
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
use oak_core::{Rational, TimeRange};
|
|
use oak_undo::undocommand::UndoCommand;
|
|
|
|
use crate::handle::get;
|
|
use crate::undocommon::{box_command, Command};
|
|
|
|
/// `TimelineMarker` — a named, colored time range on a timeline
|
|
/// (timelinemarker.h). De-Qt; `draw()` moved to the app layer.
|
|
pub struct TimelineMarker {
|
|
/// Marker time range.
|
|
time_: TimeRange,
|
|
/// Marker name (may be empty).
|
|
name_: String,
|
|
/// Marker color index.
|
|
color_: i32,
|
|
}
|
|
|
|
impl TimelineMarker {
|
|
/// Default constructor.
|
|
pub fn new() -> Self {
|
|
Self::with_time(
|
|
0,
|
|
TimeRange::new(Rational::new(0, 1), Rational::new(0, 1)),
|
|
"",
|
|
)
|
|
}
|
|
|
|
/// Construct with color, time and optional name.
|
|
pub fn with_time(color: i32, time: TimeRange, name: &str) -> Self {
|
|
Self {
|
|
time_: time,
|
|
name_: name.to_string(),
|
|
color_: color,
|
|
}
|
|
}
|
|
|
|
/// The marker's time range.
|
|
pub fn time(&self) -> &TimeRange {
|
|
&self.time_
|
|
}
|
|
|
|
/// Set the time range. De-Qt: the marker has no parent pointer, so the
|
|
/// owning list is not re-sorted here; callers use
|
|
/// `TimelineMarkerList::resort`/`resort_at` when order must be restored.
|
|
pub fn set_time(&mut self, time: TimeRange) {
|
|
self.time_ = time;
|
|
}
|
|
|
|
/// Set the time to a zero-length range at `time`.
|
|
pub fn set_time_point(&mut self, time: Rational) {
|
|
let length = self.time_.length();
|
|
self.set_time(TimeRange::new(time, time + length));
|
|
}
|
|
|
|
/// Whether another marker in the same list shares `time` as its in point.
|
|
///
|
|
/// De-Qt simplification: the marker has no parent pointer, so this always
|
|
/// returns `false`; callers that need the answer use
|
|
/// `TimelineMarkerList::get_marker_at_time` instead.
|
|
pub fn has_sibling_at_time(&self, _t: Rational) -> bool {
|
|
false
|
|
}
|
|
|
|
/// The marker's name.
|
|
pub fn name(&self) -> &str {
|
|
&self.name_
|
|
}
|
|
|
|
/// Set the marker's name.
|
|
pub fn set_name(&mut self, name: &str) {
|
|
self.name_ = name.to_string();
|
|
}
|
|
|
|
/// The marker's color index.
|
|
pub fn color(&self) -> i32 {
|
|
self.color_
|
|
}
|
|
|
|
/// Set the marker's color index.
|
|
pub fn set_color(&mut self, c: i32) {
|
|
self.color_ = c;
|
|
}
|
|
}
|
|
|
|
/// `TimelineMarkerList` — an ordered collection of markers, sorted by time
|
|
/// (timelinemarker.h).
|
|
pub struct TimelineMarkerList {
|
|
/// Markers, kept sorted by time.
|
|
markers_: Vec<TimelineMarker>,
|
|
}
|
|
|
|
impl TimelineMarkerList {
|
|
/// A new, empty list.
|
|
pub fn new() -> Self {
|
|
Self {
|
|
markers_: Vec::new(),
|
|
}
|
|
}
|
|
|
|
/// Whether the list is empty.
|
|
pub fn empty(&self) -> bool {
|
|
self.markers_.is_empty()
|
|
}
|
|
|
|
/// Number of markers.
|
|
pub fn size(&self) -> usize {
|
|
self.markers_.len()
|
|
}
|
|
|
|
/// Marker at `i`; `None` out of range.
|
|
pub fn at(&self, i: usize) -> Option<&TimelineMarker> {
|
|
self.markers_.get(i)
|
|
}
|
|
|
|
/// The last marker; `None` when empty.
|
|
pub fn back(&self) -> Option<&TimelineMarker> {
|
|
self.markers_.last()
|
|
}
|
|
|
|
/// The first marker; `None` when empty.
|
|
pub fn front(&self) -> Option<&TimelineMarker> {
|
|
self.markers_.first()
|
|
}
|
|
|
|
/// Insert a marker, keeping the list sorted by time (takes ownership).
|
|
///
|
|
/// Equal in points insert after the existing ones (`>` comparison,
|
|
/// CPP-PARITY timelinemarker.h `insert_into_list`).
|
|
pub fn add_marker(&mut self, marker: TimelineMarker) {
|
|
for i in 0..self.markers_.len() {
|
|
if self.markers_[i].time().in_() > marker.time().in_() {
|
|
self.markers_.insert(i, marker);
|
|
return;
|
|
}
|
|
}
|
|
self.markers_.push(marker);
|
|
}
|
|
|
|
/// Detach `m` from the list, returning it; `None` if not present.
|
|
pub fn remove_marker(&mut self, m: &TimelineMarker) -> Option<TimelineMarker> {
|
|
for i in 0..self.markers_.len() {
|
|
if std::ptr::eq(&self.markers_[i], m) {
|
|
return Some(self.markers_.remove(i));
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
/// First marker whose in point equals `t`; `None` if none.
|
|
pub fn get_marker_at_time(&self, t: Rational) -> Option<&TimelineMarker> {
|
|
self.markers_.iter().find(|m| m.time().in_() == t)
|
|
}
|
|
|
|
/// Marker closest to `t` (early-exits once the diff increases;
|
|
/// CPP-PARITY timelinemarker.h).
|
|
pub fn get_closest_marker_to_time(&self, t: Rational) -> Option<&TimelineMarker> {
|
|
let mut closest: Option<&TimelineMarker> = None;
|
|
let mut closest_diff = Rational::new(0, 1);
|
|
for m in &self.markers_ {
|
|
let this_diff = rational_abs(m.time().in_() - t);
|
|
if closest.is_some() && this_diff > closest_diff {
|
|
// Sorted by in point, so the distance is increasing from
|
|
// here on.
|
|
break;
|
|
}
|
|
if closest.is_none() || this_diff < closest_diff {
|
|
closest = Some(m);
|
|
closest_diff = this_diff;
|
|
}
|
|
}
|
|
closest
|
|
}
|
|
|
|
/// Re-sort `m` after its time changed.
|
|
pub fn resort(&mut self, m: &mut TimelineMarker) {
|
|
if let Some(index) = self.markers_.iter().position(|x| std::ptr::eq(x, &*m)) {
|
|
let marker = self.markers_.remove(index);
|
|
self.add_marker(marker);
|
|
}
|
|
}
|
|
|
|
/// Marker at `i`, mutably; `None` out of range.
|
|
fn at_mut(&mut self, i: usize) -> Option<&mut TimelineMarker> {
|
|
self.markers_.get_mut(i)
|
|
}
|
|
|
|
/// Remove the marker at `index`, returning it; `None` out of range.
|
|
fn remove_at(&mut self, index: usize) -> Option<TimelineMarker> {
|
|
if index < self.markers_.len() {
|
|
Some(self.markers_.remove(index))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Remove the marker at `index` and re-insert it sorted; no-op when
|
|
/// `index` is out of range. Used by the time-change command after
|
|
/// mutating a marker in place.
|
|
fn resort_at(&mut self, index: usize) {
|
|
if index < self.markers_.len() {
|
|
let marker = self.markers_.remove(index);
|
|
self.add_marker(marker);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Absolute value of a rational (oakcore-rs has no `abs`).
|
|
fn rational_abs(r: Rational) -> Rational {
|
|
if r < Rational::new(0, 1) {
|
|
Rational::new(0, 1) - r
|
|
} else {
|
|
r
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Marker undo commands. Each struct exposes prepare()/redo()/undo(); the
|
|
// undo stack wraps it through undocommon's box_command (to_command()).
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// `MarkerAddCommand` (timelinemarker.h).
|
|
pub struct MarkerAddCommand {
|
|
/// Target list (the shared marker list behind the facade's value
|
|
/// handle; `None` for an empty/null handle, which makes the command a
|
|
/// no-op).
|
|
marker_list: Option<Arc<Mutex<TimelineMarkerList>>>,
|
|
/// Marker range.
|
|
range: TimeRange,
|
|
/// Marker name.
|
|
name: String,
|
|
/// Marker color.
|
|
color: i32,
|
|
/// Whether the marker is currently in the list.
|
|
added: bool,
|
|
}
|
|
|
|
impl MarkerAddCommand {
|
|
/// Construct from range/name/color.
|
|
pub fn new(
|
|
marker_list: crate::handle::CHandle,
|
|
range: TimeRange,
|
|
name: &str,
|
|
color: i32,
|
|
) -> Self {
|
|
// SAFETY: marker-list handles box an `Arc<Mutex<TimelineMarkerList>>`
|
|
// (created by `make_owned`); reading it clones the shared `Arc`.
|
|
let list = unsafe { get::<Arc<Mutex<TimelineMarkerList>>>(&marker_list) }.cloned();
|
|
Self {
|
|
marker_list: list,
|
|
range,
|
|
name: name.to_string(),
|
|
color,
|
|
added: false,
|
|
}
|
|
}
|
|
|
|
/// Construct over an already-shared marker list (the crate's
|
|
/// Rust-typed entry; the facade-facing [`Self::new`] forwards here).
|
|
pub fn new_arc(
|
|
marker_list: Arc<Mutex<TimelineMarkerList>>,
|
|
range: TimeRange,
|
|
name: &str,
|
|
color: i32,
|
|
) -> Self {
|
|
Self {
|
|
marker_list: Some(marker_list),
|
|
range,
|
|
name: name.to_string(),
|
|
color,
|
|
added: false,
|
|
}
|
|
}
|
|
|
|
/// `redo`: add the marker, sorted.
|
|
pub fn redo(&mut self) {
|
|
if self.added {
|
|
return;
|
|
}
|
|
if let Some(list) = &self.marker_list {
|
|
let marker = TimelineMarker::with_time(self.color, self.range, &self.name);
|
|
let mut l = list.lock().unwrap_or_else(|e| e.into_inner());
|
|
l.add_marker(marker);
|
|
self.added = true;
|
|
}
|
|
}
|
|
|
|
/// `undo`: remove the added marker.
|
|
///
|
|
/// Simplification: the marker is located by its in point
|
|
/// (`get_marker_at_time`); when several markers share the in point, the
|
|
/// oldest (first in the list) is removed.
|
|
pub fn undo(&mut self) {
|
|
if !self.added {
|
|
return;
|
|
}
|
|
if let Some(list) = &self.marker_list {
|
|
let mut l = list.lock().unwrap_or_else(|e| e.into_inner());
|
|
if let Some(index) = (0..l.size())
|
|
.find(|&i| l.at(i).is_some_and(|m| m.time().in_() == self.range.in_()))
|
|
{
|
|
l.remove_at(index);
|
|
}
|
|
self.added = false;
|
|
}
|
|
}
|
|
|
|
/// Wrap as an oakundo command handle.
|
|
pub fn to_command(self) -> UndoCommand {
|
|
box_command(self)
|
|
}
|
|
}
|
|
|
|
impl Command for MarkerAddCommand {
|
|
/// `Command::redo` — the inherent method takes precedence.
|
|
fn redo(&mut self) {
|
|
self.redo();
|
|
}
|
|
|
|
/// `Command::undo` — the inherent method takes precedence.
|
|
fn undo(&mut self) {
|
|
self.undo();
|
|
}
|
|
}
|
|
|
|
/// `MarkerRemoveCommand` (timelinemarker.h).
|
|
pub struct MarkerRemoveCommand {
|
|
/// Target list (shared marker list behind the facade's value handle;
|
|
/// `None` for an empty/null handle, which makes the command a no-op).
|
|
marker_list: Option<Arc<Mutex<TimelineMarkerList>>>,
|
|
/// Index of the marker to remove.
|
|
index: usize,
|
|
/// Marker detached on `redo`, re-inserted by `undo`.
|
|
removed: Option<TimelineMarker>,
|
|
}
|
|
|
|
impl MarkerRemoveCommand {
|
|
/// Construct from list + index of the marker to remove.
|
|
pub fn new(marker_list: crate::handle::CHandle, index: usize) -> Self {
|
|
// SAFETY: as `MarkerAddCommand::new`.
|
|
let list = unsafe { get::<Arc<Mutex<TimelineMarkerList>>>(&marker_list) }.cloned();
|
|
Self {
|
|
marker_list: list,
|
|
index,
|
|
removed: None,
|
|
}
|
|
}
|
|
|
|
/// Construct over an already-shared marker list (the crate's
|
|
/// Rust-typed entry; the facade-facing [`Self::new`] forwards here).
|
|
pub fn new_arc(marker_list: Arc<Mutex<TimelineMarkerList>>, index: usize) -> Self {
|
|
Self {
|
|
marker_list: Some(marker_list),
|
|
index,
|
|
removed: None,
|
|
}
|
|
}
|
|
|
|
/// `redo`: remove the marker.
|
|
pub fn redo(&mut self) {
|
|
if self.removed.is_some() {
|
|
return;
|
|
}
|
|
if let Some(list) = &self.marker_list {
|
|
let mut l = list.lock().unwrap_or_else(|e| e.into_inner());
|
|
if let Some(removed) = l.remove_at(self.index) {
|
|
self.removed = Some(removed);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// `undo`: re-insert the marker, sorted.
|
|
pub fn undo(&mut self) {
|
|
if let Some(list) = &self.marker_list {
|
|
let mut l = list.lock().unwrap_or_else(|e| e.into_inner());
|
|
if let Some(marker) = self.removed.take() {
|
|
l.add_marker(marker);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Wrap as an oakundo command handle.
|
|
pub fn to_command(self) -> UndoCommand {
|
|
box_command(self)
|
|
}
|
|
}
|
|
|
|
impl Command for MarkerRemoveCommand {
|
|
/// `Command::redo` — the inherent method takes precedence.
|
|
fn redo(&mut self) {
|
|
self.redo();
|
|
}
|
|
|
|
/// `Command::undo` — the inherent method takes precedence.
|
|
fn undo(&mut self) {
|
|
self.undo();
|
|
}
|
|
}
|
|
|
|
/// `MarkerChangeColorCommand` (timelinemarker.h).
|
|
pub struct MarkerChangeColorCommand {
|
|
/// Target list (shared marker list behind the facade's value handle;
|
|
/// `None` for an empty/null handle, which makes the command a no-op).
|
|
marker_list: Option<Arc<Mutex<TimelineMarkerList>>>,
|
|
/// Index of the marker to change.
|
|
index: usize,
|
|
/// Color before the change.
|
|
old_color: i32,
|
|
/// New color.
|
|
new_color: i32,
|
|
}
|
|
|
|
impl MarkerChangeColorCommand {
|
|
/// Construct from list + index + new color, capturing the current color
|
|
/// as old.
|
|
pub fn new(marker_list: crate::handle::CHandle, index: usize, new_color: i32) -> Self {
|
|
// SAFETY: as `MarkerAddCommand::new`.
|
|
let list = unsafe { get::<Arc<Mutex<TimelineMarkerList>>>(&marker_list) }.cloned();
|
|
let old_color = list
|
|
.as_ref()
|
|
.and_then(|l| {
|
|
let l = l.lock().unwrap_or_else(|e| e.into_inner());
|
|
l.at(index).map(|m| m.color())
|
|
})
|
|
.unwrap_or(0);
|
|
Self {
|
|
marker_list: list,
|
|
index,
|
|
old_color,
|
|
new_color,
|
|
}
|
|
}
|
|
|
|
/// Construct over an already-shared marker list (the crate's
|
|
/// Rust-typed entry; the facade-facing [`Self::new`] forwards here).
|
|
pub fn new_arc(
|
|
marker_list: Arc<Mutex<TimelineMarkerList>>,
|
|
index: usize,
|
|
new_color: i32,
|
|
) -> Self {
|
|
let old_color = marker_list
|
|
.lock()
|
|
.unwrap_or_else(|e| e.into_inner())
|
|
.at(index)
|
|
.map(|m| m.color())
|
|
.unwrap_or(0);
|
|
Self {
|
|
marker_list: Some(marker_list),
|
|
index,
|
|
old_color,
|
|
new_color,
|
|
}
|
|
}
|
|
|
|
/// `redo`: apply the new color.
|
|
pub fn redo(&mut self) {
|
|
if let Some(list) = &self.marker_list {
|
|
let mut l = list.lock().unwrap_or_else(|e| e.into_inner());
|
|
if let Some(m) = l.at_mut(self.index) {
|
|
m.set_color(self.new_color);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// `undo`: restore the old color.
|
|
pub fn undo(&mut self) {
|
|
if let Some(list) = &self.marker_list {
|
|
let mut l = list.lock().unwrap_or_else(|e| e.into_inner());
|
|
if let Some(m) = l.at_mut(self.index) {
|
|
m.set_color(self.old_color);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Wrap as an oakundo command handle.
|
|
pub fn to_command(self) -> UndoCommand {
|
|
box_command(self)
|
|
}
|
|
}
|
|
|
|
impl Command for MarkerChangeColorCommand {
|
|
/// `Command::redo` — the inherent method takes precedence.
|
|
fn redo(&mut self) {
|
|
self.redo();
|
|
}
|
|
|
|
/// `Command::undo` — the inherent method takes precedence.
|
|
fn undo(&mut self) {
|
|
self.undo();
|
|
}
|
|
}
|
|
|
|
/// `MarkerChangeNameCommand` (timelinemarker.h).
|
|
pub struct MarkerChangeNameCommand {
|
|
/// Target list (shared marker list behind the facade's value handle;
|
|
/// `None` for an empty/null handle, which makes the command a no-op).
|
|
marker_list: Option<Arc<Mutex<TimelineMarkerList>>>,
|
|
/// Index of the marker to change.
|
|
index: usize,
|
|
/// Name before the change.
|
|
old_name: String,
|
|
/// New name.
|
|
new_name: String,
|
|
}
|
|
|
|
impl MarkerChangeNameCommand {
|
|
/// Construct from list + index + new name, capturing the current name as
|
|
/// old.
|
|
pub fn new(marker_list: crate::handle::CHandle, index: usize, name: &str) -> Self {
|
|
// SAFETY: as `MarkerAddCommand::new`.
|
|
let list = unsafe { get::<Arc<Mutex<TimelineMarkerList>>>(&marker_list) }.cloned();
|
|
let old_name = list
|
|
.as_ref()
|
|
.and_then(|l| {
|
|
let l = l.lock().unwrap_or_else(|e| e.into_inner());
|
|
l.at(index).map(|m| m.name().to_string())
|
|
})
|
|
.unwrap_or_default();
|
|
Self {
|
|
marker_list: list,
|
|
index,
|
|
old_name,
|
|
new_name: name.to_string(),
|
|
}
|
|
}
|
|
|
|
/// Construct over an already-shared marker list (the crate's
|
|
/// Rust-typed entry; the facade-facing [`Self::new`] forwards here).
|
|
pub fn new_arc(marker_list: Arc<Mutex<TimelineMarkerList>>, index: usize, name: &str) -> Self {
|
|
let old_name = marker_list
|
|
.lock()
|
|
.unwrap_or_else(|e| e.into_inner())
|
|
.at(index)
|
|
.map(|m| m.name().to_string())
|
|
.unwrap_or_default();
|
|
Self {
|
|
marker_list: Some(marker_list),
|
|
index,
|
|
old_name,
|
|
new_name: name.to_string(),
|
|
}
|
|
}
|
|
|
|
/// `redo`: apply the new name.
|
|
pub fn redo(&mut self) {
|
|
if let Some(list) = &self.marker_list {
|
|
let mut l = list.lock().unwrap_or_else(|e| e.into_inner());
|
|
if let Some(m) = l.at_mut(self.index) {
|
|
m.set_name(&self.new_name);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// `undo`: restore the old name.
|
|
pub fn undo(&mut self) {
|
|
if let Some(list) = &self.marker_list {
|
|
let mut l = list.lock().unwrap_or_else(|e| e.into_inner());
|
|
if let Some(m) = l.at_mut(self.index) {
|
|
m.set_name(&self.old_name);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Wrap as an oakundo command handle.
|
|
pub fn to_command(self) -> UndoCommand {
|
|
box_command(self)
|
|
}
|
|
}
|
|
|
|
impl Command for MarkerChangeNameCommand {
|
|
/// `Command::redo` — the inherent method takes precedence.
|
|
fn redo(&mut self) {
|
|
self.redo();
|
|
}
|
|
|
|
/// `Command::undo` — the inherent method takes precedence.
|
|
fn undo(&mut self) {
|
|
self.undo();
|
|
}
|
|
}
|
|
|
|
/// `MarkerChangeTimeCommand` (timelinemarker.h). The old range is captured at
|
|
/// construction when not supplied.
|
|
pub struct MarkerChangeTimeCommand {
|
|
/// Target list (shared marker list behind the facade's value handle;
|
|
/// `None` for an empty/null handle, which makes the command a no-op).
|
|
marker_list: Option<Arc<Mutex<TimelineMarkerList>>>,
|
|
/// Index of the marker to change.
|
|
index: usize,
|
|
/// Time range before the change.
|
|
old_time: TimeRange,
|
|
/// New time range.
|
|
new_time: TimeRange,
|
|
}
|
|
|
|
impl MarkerChangeTimeCommand {
|
|
/// Construct from list + index + new time, capturing the current range as
|
|
/// old.
|
|
pub fn new(marker_list: crate::handle::CHandle, index: usize, time: TimeRange) -> Self {
|
|
// SAFETY: as `MarkerAddCommand::new`.
|
|
let list = unsafe { get::<Arc<Mutex<TimelineMarkerList>>>(&marker_list) }.cloned();
|
|
let old_time = list
|
|
.as_ref()
|
|
.and_then(|l| {
|
|
let l = l.lock().unwrap_or_else(|e| e.into_inner());
|
|
l.at(index).map(|m| *m.time())
|
|
})
|
|
.unwrap_or_else(|| TimeRange::new(Rational::new(0, 1), Rational::new(0, 1)));
|
|
Self {
|
|
marker_list: list,
|
|
index,
|
|
old_time,
|
|
new_time: time,
|
|
}
|
|
}
|
|
|
|
/// Construct over an already-shared marker list (the crate's
|
|
/// Rust-typed entry; the facade-facing [`Self::new`] forwards here).
|
|
pub fn new_arc(
|
|
marker_list: Arc<Mutex<TimelineMarkerList>>,
|
|
index: usize,
|
|
time: TimeRange,
|
|
) -> Self {
|
|
let old_time = marker_list
|
|
.lock()
|
|
.unwrap_or_else(|e| e.into_inner())
|
|
.at(index)
|
|
.map(|m| *m.time())
|
|
.unwrap_or_else(|| TimeRange::new(Rational::new(0, 1), Rational::new(0, 1)));
|
|
Self {
|
|
marker_list: Some(marker_list),
|
|
index,
|
|
old_time,
|
|
new_time: time,
|
|
}
|
|
}
|
|
|
|
/// `redo`: apply the new time (resorts).
|
|
pub fn redo(&mut self) {
|
|
if let Some(list) = &self.marker_list {
|
|
let mut l = list.lock().unwrap_or_else(|e| e.into_inner());
|
|
if let Some(m) = l.at_mut(self.index) {
|
|
m.set_time(self.new_time);
|
|
}
|
|
l.resort_at(self.index);
|
|
}
|
|
}
|
|
|
|
/// `undo`: restore the old time (resorts).
|
|
pub fn undo(&mut self) {
|
|
if let Some(list) = &self.marker_list {
|
|
let mut l = list.lock().unwrap_or_else(|e| e.into_inner());
|
|
if let Some(m) = l.at_mut(self.index) {
|
|
m.set_time(self.old_time);
|
|
}
|
|
l.resort_at(self.index);
|
|
}
|
|
}
|
|
|
|
/// Wrap as an oakundo command handle.
|
|
pub fn to_command(self) -> UndoCommand {
|
|
box_command(self)
|
|
}
|
|
}
|
|
|
|
impl Command for MarkerChangeTimeCommand {
|
|
/// `Command::redo` — the inherent method takes precedence.
|
|
fn redo(&mut self) {
|
|
self.redo();
|
|
}
|
|
|
|
/// `Command::undo` — the inherent method takes precedence.
|
|
fn undo(&mut self) {
|
|
self.undo();
|
|
}
|
|
}
|