/* ENSnano, a 3d graphical application for DNA nanostructures. Copyright (C) 2021 Nicolas Levy and Nicolas Schabanel 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 . */ //! This modules defines the `Operation` trait and several struct that implement it. //! //! An structure that implements `Operation` can produce an `UndoableOpperation` that will have an //! effect on the design. //! //! Moreover, these operations are meant to be modifiable via GUI component or user interaction. use super::{DesignRotation, DesignTranslation, GridDescriptor, GridHelixDescriptor, UndoableOp}; use crate::design::{ GridTypeDescr, Helix, Hyperboloid, IsometryTarget, Nucl, Strand, StrandBuilder, StrandState, }; use std::sync::Arc; use ultraviolet::{Bivec3, Rotor3, Vec3}; pub enum ParameterField { Choice(Vec), Value, } pub struct Parameter { pub field: ParameterField, pub name: String, } pub trait Operation: std::fmt::Debug + Sync + Send { /// The set of parameters that can be modified via a GUI component fn parameters(&self) -> Vec; /// The values associated to the parameters. fn values(&self) -> Vec; /// Return an opperation whose effect cancels the effect of `self`. fn reverse(&self) -> Arc; /// The effect of self that must be sent as a notifications to the targeted designs fn effect(&self) -> UndoableOp; /// A description of self of display in the GUI fn description(&self) -> String; /// The targeted designs of self. fn target(&self) -> usize; /// Produce an new opperation by setting the value of the `n`-th parameter to `val`. fn with_new_value(&self, n: usize, val: String) -> Option>; fn descr(&self) -> OperationDescriptor; /// If `other` is compatible with `self` return the operation whose effect is equivalent to /// applying the effects of `other` and then `self`. fn compose(&self, other: &dyn Operation) -> Option>; fn must_reverse(&self) -> bool { true } fn drop_undo(&self) -> bool { false } fn redoable(&self) -> bool { true } } #[derive(Clone, Debug)] pub struct GridRotation { pub origin: Vec3, pub design_id: usize, pub grid_id: usize, pub angle: f32, pub plane: Bivec3, } impl Operation for GridRotation { fn descr(&self) -> OperationDescriptor { OperationDescriptor::GridRotation(self.design_id, self.grid_id, self.plane) } fn compose(&self, other: &dyn Operation) -> Option> { if self.descr() == other.descr() { let angle = other.values()[0].parse::().unwrap().to_radians(); Some(Arc::new(Self { angle: self.angle + angle, ..*self })) } else { None } } fn parameters(&self) -> Vec { vec![Parameter { field: ParameterField::Value, name: String::from("angle"), }] } fn values(&self) -> Vec { vec![self.angle.to_degrees().to_string()] } fn reverse(&self) -> Arc { Arc::new(Self { angle: -self.angle, ..*self }) } fn effect(&self) -> UndoableOp { let rotor = Rotor3::from_angle_plane(self.angle, self.plane); UndoableOp::Rotation(DesignRotation { rotation: rotor, origin: self.origin, target: IsometryTarget::Grid(self.grid_id as u32), }) } fn description(&self) -> String { format!("Rotate grid {} of design {}", self.grid_id, self.design_id) } fn target(&self) -> usize { self.design_id } fn with_new_value(&self, n: usize, val: String) -> Option> { if n == 0 { let degrees: f32 = val.parse().ok()?; Some(Arc::new(Self { angle: degrees.to_radians(), ..*self })) } else { None } } } #[derive(Clone, Debug)] pub struct HelixRotation { pub origin: Vec3, pub design_id: usize, pub helix_id: usize, pub angle: f32, pub plane: Bivec3, } impl Operation for HelixRotation { fn descr(&self) -> OperationDescriptor { OperationDescriptor::HelixRotation(self.design_id, self.helix_id, self.plane) } fn compose(&self, other: &dyn Operation) -> Option> { if self.descr() == other.descr() { let angle = other.values()[0].parse::().unwrap().to_radians(); Some(Arc::new(Self { angle: self.angle + angle, ..*self })) } else { None } } fn parameters(&self) -> Vec { vec![Parameter { field: ParameterField::Value, name: String::from("angle"), }] } fn values(&self) -> Vec { vec![self.angle.to_degrees().to_string()] } fn reverse(&self) -> Arc { Arc::new(Self { angle: -self.angle, ..*self }) } fn effect(&self) -> UndoableOp { let rotor = Rotor3::from_angle_plane(self.angle, self.plane); UndoableOp::Rotation(DesignRotation { rotation: rotor, origin: self.origin, target: IsometryTarget::Helix(self.helix_id as u32, false), }) } fn description(&self) -> String { format!( "Rotate helix {} of design {}", self.helix_id, self.design_id ) } fn target(&self) -> usize { self.design_id } fn with_new_value(&self, n: usize, val: String) -> Option> { if n == 0 { let degrees: f32 = val.parse().ok()?; Some(Arc::new(Self { angle: degrees.to_radians(), ..*self })) } else { None } } } #[derive(Debug, Clone)] pub struct DesignViewRotation { pub origin: Vec3, pub design_id: usize, pub angle: f32, pub plane: Bivec3, } impl Operation for DesignViewRotation { fn descr(&self) -> OperationDescriptor { OperationDescriptor::DesignRotation(self.design_id, self.plane) } fn compose(&self, other: &dyn Operation) -> Option> { if self.descr() == other.descr() { let angle = other.values()[0].parse::().unwrap().to_radians(); Some(Arc::new(Self { angle: self.angle + angle, ..*self })) } else { None } } fn parameters(&self) -> Vec { vec![Parameter { field: ParameterField::Value, name: String::from("angle"), }] } fn values(&self) -> Vec { vec![self.angle.to_degrees().to_string()] } fn reverse(&self) -> Arc { Arc::new(Self { angle: -self.angle, ..*self }) } fn effect(&self) -> UndoableOp { let rotor = Rotor3::from_angle_plane(self.angle, self.plane); UndoableOp::Rotation(DesignRotation { rotation: rotor, origin: self.origin, target: IsometryTarget::Design, }) } fn description(&self) -> String { format!("Rotate view of design {}", self.design_id) } fn target(&self) -> usize { self.design_id } fn with_new_value(&self, n: usize, val: String) -> Option> { if n == 0 { let degrees: f32 = val.parse().ok()?; Some(Arc::new(Self { angle: degrees.to_radians(), ..*self })) } else { None } } } #[derive(Debug, Clone)] pub struct DesignViewTranslation { pub design_id: usize, pub right: Vec3, pub top: Vec3, pub dir: Vec3, pub x: f32, pub y: f32, pub z: f32, } impl Operation for DesignViewTranslation { fn descr(&self) -> OperationDescriptor { OperationDescriptor::DesignTranslation(self.design_id) } fn compose(&self, other: &dyn Operation) -> Option> { if self.descr() == other.descr() { let x = other.values()[0].parse::().unwrap(); let y = other.values()[1].parse::().unwrap(); let z = other.values()[2].parse::().unwrap(); Some(Arc::new(Self { x: self.x + x, y: self.y + y, z: self.z + z, ..*self })) } else { None } } fn parameters(&self) -> Vec { vec![ Parameter { field: ParameterField::Value, name: String::from("x"), }, Parameter { field: ParameterField::Value, name: String::from("y"), }, Parameter { field: ParameterField::Value, name: String::from("z"), }, ] } fn values(&self) -> Vec { vec![self.x.to_string(), self.y.to_string(), self.z.to_string()] } fn reverse(&self) -> Arc { Arc::new(Self { x: -self.x, y: -self.y, z: -self.z, ..*self }) } fn effect(&self) -> UndoableOp { let translation = self.x * self.right + self.y * self.top + self.z * self.dir; UndoableOp::Translation(DesignTranslation { translation, target: IsometryTarget::Design, }) } fn description(&self) -> String { format!("Translate design {}", self.design_id) } fn target(&self) -> usize { self.design_id } fn with_new_value(&self, n: usize, val: String) -> Option> { match n { 0 => { let new_x: f32 = val.parse().ok()?; Some(Arc::new(Self { x: new_x, ..*self })) } 1 => { let new_y: f32 = val.parse().ok()?; Some(Arc::new(Self { y: new_y, ..*self })) } 2 => { let new_z: f32 = val.parse().ok()?; Some(Arc::new(Self { z: new_z, ..*self })) } _ => None, } } } #[derive(Debug, Clone)] pub struct HelixTranslation { pub design_id: usize, pub helix_id: usize, pub right: Vec3, pub top: Vec3, pub dir: Vec3, pub x: f32, pub y: f32, pub z: f32, pub snap: bool, } impl Operation for HelixTranslation { fn descr(&self) -> OperationDescriptor { OperationDescriptor::HelixTranslation(self.design_id, self.helix_id) } fn compose(&self, other: &dyn Operation) -> Option> { if self.descr() == other.descr() { let x = other.values()[0].parse::().unwrap(); let y = other.values()[1].parse::().unwrap(); let z = other.values()[2].parse::().unwrap(); Some(Arc::new(Self { x: self.x + x, y: self.y + y, z: self.z + z, ..*self })) } else { None } } fn parameters(&self) -> Vec { vec![ Parameter { field: ParameterField::Value, name: String::from("x"), }, Parameter { field: ParameterField::Value, name: String::from("y"), }, Parameter { field: ParameterField::Value, name: String::from("z"), }, ] } fn values(&self) -> Vec { vec![self.x.to_string(), self.y.to_string(), self.z.to_string()] } fn reverse(&self) -> Arc { Arc::new(Self { x: -self.x, y: -self.y, z: -self.z, ..*self }) } fn effect(&self) -> UndoableOp { let translation = self.x * self.right + self.y * self.top + self.z * self.dir; UndoableOp::Translation(DesignTranslation { translation, target: IsometryTarget::Helix(self.helix_id as u32, self.snap), }) } fn description(&self) -> String { format!( "Translate helix {} of design {}", self.helix_id, self.design_id ) } fn target(&self) -> usize { self.design_id } fn with_new_value(&self, n: usize, val: String) -> Option> { match n { 0 => { let new_x: f32 = val.parse().ok()?; Some(Arc::new(Self { x: new_x, ..*self })) } 1 => { let new_y: f32 = val.parse().ok()?; Some(Arc::new(Self { y: new_y, ..*self })) } 2 => { let new_z: f32 = val.parse().ok()?; Some(Arc::new(Self { z: new_z, ..*self })) } _ => None, } } fn must_reverse(&self) -> bool { false } } #[derive(Debug, Clone)] pub struct GridTranslation { pub design_id: usize, pub grid_id: usize, pub right: Vec3, pub top: Vec3, pub dir: Vec3, pub x: f32, pub y: f32, pub z: f32, } impl Operation for GridTranslation { fn descr(&self) -> OperationDescriptor { OperationDescriptor::GridTranslation(self.design_id, self.grid_id) } fn compose(&self, other: &dyn Operation) -> Option> { if self.descr() == other.descr() { let x = other.values()[0].parse::().unwrap(); let y = other.values()[1].parse::().unwrap(); let z = other.values()[2].parse::().unwrap(); Some(Arc::new(Self { x: self.x + x, y: self.y + y, z: self.z + z, ..*self })) } else { None } } fn parameters(&self) -> Vec { vec![ Parameter { field: ParameterField::Value, name: String::from("x"), }, Parameter { field: ParameterField::Value, name: String::from("y"), }, Parameter { field: ParameterField::Value, name: String::from("z"), }, ] } fn values(&self) -> Vec { vec![self.x.to_string(), self.y.to_string(), self.z.to_string()] } fn reverse(&self) -> Arc { Arc::new(Self { x: -self.x, y: -self.y, z: -self.z, ..*self }) } fn effect(&self) -> UndoableOp { let translation = self.x * self.right + self.y * self.top + self.z * self.dir; UndoableOp::Translation(DesignTranslation { translation, target: IsometryTarget::Grid(self.grid_id as u32), }) } fn description(&self) -> String { format!( "Translate grid {} of design {}", self.grid_id, self.design_id ) } fn target(&self) -> usize { self.design_id } fn with_new_value(&self, n: usize, val: String) -> Option> { match n { 0 => { let new_x: f32 = val.parse().ok()?; Some(Arc::new(Self { x: new_x, ..*self })) } 1 => { let new_y: f32 = val.parse().ok()?; Some(Arc::new(Self { y: new_y, ..*self })) } 2 => { let new_z: f32 = val.parse().ok()?; Some(Arc::new(Self { z: new_z, ..*self })) } _ => None, } } } #[derive(Debug, Clone)] pub struct GridHelixCreation { pub design_id: usize, pub grid_id: usize, pub x: isize, pub y: isize, pub position: isize, pub length: usize, } impl Operation for GridHelixCreation { fn descr(&self) -> OperationDescriptor { OperationDescriptor::GridHelixCreation(self.design_id, self.grid_id) } fn compose(&self, _other: &dyn Operation) -> Option> { None } fn parameters(&self) -> Vec { vec![] } fn values(&self) -> Vec { vec![self.x.to_string(), self.y.to_string()] } fn reverse(&self) -> Arc { Arc::new(GridHelixDeletion { x: self.x, y: self.y, design_id: self.design_id, grid_id: self.grid_id, position: self.position, length: self.length, }) } fn effect(&self) -> UndoableOp { UndoableOp::AddGridHelix( GridHelixDescriptor { grid_id: self.grid_id, x: self.x, y: self.y, }, self.position, self.length, ) } fn description(&self) -> String { format!( "Create helix on grid {} of design {}", self.grid_id, self.design_id ) } fn target(&self) -> usize { self.design_id } fn with_new_value(&self, n: usize, val: String) -> Option> { match n { 0 => { let new_x: f32 = val.parse().ok()?; Some(Arc::new(Self { x: new_x as isize, ..*self })) } 1 => { let new_y: f32 = val.parse().ok()?; Some(Arc::new(Self { y: new_y as isize, ..*self })) } _ => None, } } } #[derive(Debug, Clone)] pub struct GridHelixDeletion { design_id: usize, grid_id: usize, x: isize, y: isize, position: isize, length: usize, } impl Operation for GridHelixDeletion { fn descr(&self) -> OperationDescriptor { OperationDescriptor::GridHelixCreation(self.design_id, self.grid_id) } fn compose(&self, _other: &dyn Operation) -> Option> { None } fn parameters(&self) -> Vec { vec![] } fn values(&self) -> Vec { vec![self.x.to_string(), self.y.to_string()] } fn reverse(&self) -> Arc { Arc::new(GridHelixCreation { x: self.x, y: self.y, design_id: self.design_id, grid_id: self.grid_id, position: self.position, length: self.length, }) } fn effect(&self) -> UndoableOp { UndoableOp::RmGridHelix( GridHelixDescriptor { grid_id: self.grid_id, x: self.x, y: self.y, }, self.position, self.length, ) } fn description(&self) -> String { format!( "Create helix on grid {} of design {}", self.grid_id, self.design_id ) } fn target(&self) -> usize { self.design_id } fn with_new_value(&self, n: usize, val: String) -> Option> { match n { 0 => { let new_x: f32 = val.parse().ok()?; Some(Arc::new(Self { x: new_x as isize, ..*self })) } 1 => { let new_y: f32 = val.parse().ok()?; Some(Arc::new(Self { y: new_y as isize, ..*self })) } _ => None, } } } #[derive(Clone, Debug)] pub struct RawHelixCreation { pub helix: Helix, pub helix_id: usize, pub delete: bool, pub design_id: usize, } impl Operation for RawHelixCreation { fn descr(&self) -> OperationDescriptor { OperationDescriptor::RawHelixCreation } fn compose(&self, _other: &dyn Operation) -> Option> { None } fn parameters(&self) -> Vec { vec![] } fn values(&self) -> Vec { vec![] } fn reverse(&self) -> Arc { Arc::new(RawHelixCreation { delete: !self.delete, ..self.clone() }) } fn effect(&self) -> UndoableOp { UndoableOp::RawHelixCreation { helix: self.helix.clone(), h_id: self.helix_id, delete: self.delete, } } fn description(&self) -> String { if self.delete { format!("Delete grid") } else { format!("Create grid") } } fn target(&self) -> usize { self.design_id } fn with_new_value(&self, _n: usize, _val: String) -> Option> { None } } #[derive(Clone, Debug)] /// Cut a strand at a given nucleotide. /// /// If the nucleotide is the 3' end of a cross-over, it will be the 5' end of the 3' half of the /// split. /// In all other cases, it will be the 3' end of the 5' end of the split. pub struct Cut { pub strand: Strand, pub nucl: Nucl, pub strand_id: usize, pub undo: bool, pub design_id: usize, } impl Operation for Cut { fn descr(&self) -> OperationDescriptor { OperationDescriptor::Cut } fn compose(&self, _other: &dyn Operation) -> Option> { None } fn parameters(&self) -> Vec { vec![] } fn values(&self) -> Vec { vec![] } fn reverse(&self) -> Arc { Arc::new(Cut { undo: !self.undo, ..self.clone() }) } fn effect(&self) -> UndoableOp { if self.strand.length() < 2 { UndoableOp::RmStrand { strand: self.strand.clone(), strand_id: self.strand_id, undo: self.undo, } } else { UndoableOp::Cut { nucl: self.nucl, strand: self.strand.clone(), s_id: self.strand_id, undo: self.undo, } } } fn description(&self) -> String { if self.undo { format!("Undo Cut") } else { format!("Do Cut") } } fn target(&self) -> usize { self.design_id } fn with_new_value(&self, _n: usize, _val: String) -> Option> { None } } #[derive(Clone, Debug)] pub struct Xover { pub strand_5prime: Strand, pub strand_3prime: Strand, pub prime5_id: usize, pub prime3_id: usize, pub undo: bool, pub design_id: usize, } impl Operation for Xover { fn descr(&self) -> OperationDescriptor { OperationDescriptor::Xover } fn compose(&self, _other: &dyn Operation) -> Option> { None } fn parameters(&self) -> Vec { vec![] } fn values(&self) -> Vec { vec![] } fn reverse(&self) -> Arc { Arc::new(Xover { undo: !self.undo, ..self.clone() }) } fn effect(&self) -> UndoableOp { UndoableOp::Xover { strand_5prime: self.strand_5prime.clone(), strand_3prime: self.strand_3prime.clone(), prime5_id: self.prime5_id, prime3_id: self.prime3_id, undo: self.undo, } } fn description(&self) -> String { if self.undo { format!("Undo Cut") } else { format!("Do Cut") } } fn target(&self) -> usize { self.design_id } fn with_new_value(&self, _n: usize, _val: String) -> Option> { None } } /// Delete a strand #[derive(Clone, Debug)] pub struct RmStrand { pub strand: Strand, pub strand_id: usize, pub undo: bool, pub design_id: usize, } impl Operation for RmStrand { fn descr(&self) -> OperationDescriptor { OperationDescriptor::CrossCut } fn compose(&self, _other: &dyn Operation) -> Option> { None } fn parameters(&self) -> Vec { vec![] } fn values(&self) -> Vec { vec![] } fn reverse(&self) -> Arc { Arc::new(RmStrand { undo: !self.undo, ..self.clone() }) } fn effect(&self) -> UndoableOp { UndoableOp::RmStrand { strand: self.strand.clone(), strand_id: self.strand_id, undo: self.undo, } } fn description(&self) -> String { if self.undo { format!("Undo Cut") } else { format!("Do Cut") } } fn target(&self) -> usize { self.design_id } fn with_new_value(&self, _n: usize, _val: String) -> Option> { None } } /// Cut the target strand at nucl, and make a cross over from the source strand. #[derive(Clone, Debug)] pub struct CrossCut { pub source_strand: Strand, pub target_strand: Strand, pub source_id: usize, pub target_id: usize, pub nucl: Nucl, /// True if the target strand will be the 3 prime part of the merged strand pub target_3prime: bool, pub undo: bool, pub design_id: usize, } impl Operation for CrossCut { fn descr(&self) -> OperationDescriptor { OperationDescriptor::CrossCut } fn compose(&self, _other: &dyn Operation) -> Option> { None } fn parameters(&self) -> Vec { vec![] } fn values(&self) -> Vec { vec![] } fn reverse(&self) -> Arc { Arc::new(CrossCut { undo: !self.undo, ..self.clone() }) } fn effect(&self) -> UndoableOp { UndoableOp::CrossCut { source_strand: self.source_strand.clone(), target_strand: self.target_strand.clone(), source_id: self.source_id, target_id: self.target_id, target_3prime: self.target_3prime, nucl: self.nucl, undo: self.undo, } } fn description(&self) -> String { if self.undo { format!("Undo Cut") } else { format!("Do Cut") } } fn target(&self) -> usize { self.design_id } fn with_new_value(&self, _n: usize, _val: String) -> Option> { None } } #[derive(Clone, Debug)] pub struct CreateGrid { pub position: Vec3, pub orientation: Rotor3, pub grid_type: GridTypeDescr, pub delete: bool, pub design_id: usize, } impl Operation for CreateGrid { fn descr(&self) -> OperationDescriptor { OperationDescriptor::CreateGrid } fn compose(&self, _other: &dyn Operation) -> Option> { None } fn parameters(&self) -> Vec { vec![Parameter { field: ParameterField::Choice(vec![String::from("Square"), String::from("Honeycomb")]), name: String::from("Grid type"), }] } fn values(&self) -> Vec { vec![self.grid_type.to_string()] } fn reverse(&self) -> Arc { Arc::new(CreateGrid { delete: !self.delete, ..*self }) } fn effect(&self) -> UndoableOp { if self.delete { UndoableOp::RmGrid } else { UndoableOp::AddGrid(GridDescriptor { position: self.position, orientation: self.orientation, grid_type: self.grid_type, }) } } fn description(&self) -> String { if self.delete { format!("Delete grid") } else { format!("Create grid") } } fn target(&self) -> usize { self.design_id } fn with_new_value(&self, n: usize, val: String) -> Option> { match n { 0 => match val.as_str() { "Square" => Some(Arc::new(Self { grid_type: GridTypeDescr::Square, ..*self })), "Honeycomb" => Some(Arc::new(Self { grid_type: GridTypeDescr::Honeycomb, ..*self })), _ => None, }, _ => None, } } } #[derive(Clone)] pub struct BigStrandModification { pub initial_state: StrandState, pub final_state: StrandState, pub reverse: bool, pub design_id: usize, } impl std::fmt::Debug for BigStrandModification { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("BigStrandModification") .field("reverse", &self.reverse) .finish() } } impl Operation for BigStrandModification { fn descr(&self) -> OperationDescriptor { OperationDescriptor::BigStrandModification } fn compose(&self, _other: &dyn Operation) -> Option> { None } fn parameters(&self) -> Vec { vec![] } fn values(&self) -> Vec { vec![] } fn reverse(&self) -> Arc { Arc::new(BigStrandModification { reverse: !self.reverse, ..self.clone() }) } fn effect(&self) -> UndoableOp { if self.reverse { UndoableOp::NewStrandState(self.initial_state.clone()) } else { UndoableOp::NewStrandState(self.final_state.clone()) } } fn description(&self) -> String { if self.reverse { format!("Reverse Big Change") } else { format!("Redo Big Change") } } fn target(&self) -> usize { self.design_id } fn with_new_value(&self, _n: usize, _val: String) -> Option> { None } } #[derive(Clone, Debug)] pub struct NewHyperboloid { pub position: Vec3, pub orientation: Rotor3, pub hyperboloid: Hyperboloid, pub delete: bool, pub design_id: usize, } impl Operation for NewHyperboloid { fn descr(&self) -> OperationDescriptor { OperationDescriptor::CreateGrid } fn compose(&self, _other: &dyn Operation) -> Option> { None } fn parameters(&self) -> Vec { vec![] } fn values(&self) -> Vec { vec![] } fn reverse(&self) -> Arc { Arc::new(NewHyperboloid { delete: !self.delete, ..self.clone() }) } fn effect(&self) -> UndoableOp { if self.delete { UndoableOp::ClearHyperboloid } else { UndoableOp::NewHyperboloid { position: self.position, orientation: self.orientation, hyperboloid: self.hyperboloid.clone(), } } } fn description(&self) -> String { if self.delete { format!("Delete nanotube") } else { format!("Create nanotube") } } fn target(&self) -> usize { self.design_id } fn with_new_value(&self, _n: usize, _val: String) -> Option> { None } fn drop_undo(&self) -> bool { true } } #[derive(Debug)] pub struct StrandConstruction { pub builder: Box, pub redo: Option, pub color: u32, } impl Operation for StrandConstruction { fn descr(&self) -> OperationDescriptor { OperationDescriptor::BuildStrand(self.builder.get_timestamp()) } fn compose(&self, _other: &dyn Operation) -> Option> { None } fn parameters(&self) -> Vec { vec![] } fn values(&self) -> Vec { vec![] } fn reverse(&self) -> Arc { let redo = self.redo.xor(Some(self.color)); Arc::new(StrandConstruction { builder: self.builder.clone(), redo, color: self.color, }) } fn effect(&self) -> UndoableOp { if let Some(color) = self.redo { let remake = if self.builder.created_de_novo() { Some((self.builder.get_strand_id(), color)) } else { None }; UndoableOp::MoveBuilder(self.builder.clone(), remake) } else { UndoableOp::ResetBuilder(self.builder.clone()) } } fn description(&self) -> String { "Building strand".to_string() } fn target(&self) -> usize { self.builder.get_design_id() as usize } fn with_new_value(&self, _n: usize, _val: String) -> Option> { None } fn must_reverse(&self) -> bool { false } } #[derive(Clone)] pub struct RigidGridSimulation { pub initial_state: crate::design::GridSystemState, pub design_id: usize, } impl std::fmt::Debug for RigidGridSimulation { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("RigidGridSimulation").finish() } } impl Operation for RigidGridSimulation { fn descr(&self) -> OperationDescriptor { OperationDescriptor::BigStrandModification } fn compose(&self, _other: &dyn Operation) -> Option> { None } fn parameters(&self) -> Vec { vec![] } fn values(&self) -> Vec { vec![] } fn reverse(&self) -> Arc { Arc::new(self.clone()) } fn effect(&self) -> UndoableOp { UndoableOp::UndoGridSimulation(self.initial_state.clone()) } fn description(&self) -> String { format!("Undo grid simulation") } fn target(&self) -> usize { self.design_id } fn with_new_value(&self, _n: usize, _val: String) -> Option> { None } fn redoable(&self) -> bool { false } } #[derive(Clone)] pub struct RigidHelixSimulation { pub initial_state: crate::design::RigidHelixState, pub design_id: usize, } impl std::fmt::Debug for RigidHelixSimulation { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("RigidHelixSimulation").finish() } } impl Operation for RigidHelixSimulation { fn descr(&self) -> OperationDescriptor { OperationDescriptor::BigStrandModification } fn compose(&self, _other: &dyn Operation) -> Option> { None } fn parameters(&self) -> Vec { vec![] } fn values(&self) -> Vec { vec![] } fn reverse(&self) -> Arc { Arc::new(self.clone()) } fn effect(&self) -> UndoableOp { UndoableOp::UndoHelixSimulation(self.initial_state.clone()) } fn description(&self) -> String { format!("Undo helix simulation") } fn target(&self) -> usize { self.design_id } fn with_new_value(&self, _n: usize, _val: String) -> Option> { None } fn redoable(&self) -> bool { false } } #[derive(Debug)] /// A description of an operation. Two opperations whose `descr` is equal are considered to be the /// same operation with different parameters. pub enum OperationDescriptor { DesignTranslation(usize), DesignRotation(usize, Bivec3), HelixRotation(usize, usize, Bivec3), HelixTranslation(usize, usize), GridRotation(usize, usize, Bivec3), GridTranslation(usize, usize), GridHelixCreation(usize, usize), GridHelixDeletion(usize, usize), RawHelixCreation, Cut, CrossCut, Xover, RmStrand, BuildStrand(std::time::SystemTime), CreateGrid, BigStrandModification, } impl PartialEq for OperationDescriptor { fn eq(&self, rhs: &Self) -> bool { use OperationDescriptor::*; match (self, rhs) { (DesignTranslation(d1), DesignTranslation(d2)) => d1 == d2, (DesignRotation(d1, bv1), DesignRotation(d2, bv2)) => { d1 == d2 && (*bv1 - *bv2).mag() < 1e-3 } (HelixRotation(d1, h1, bv1), HelixRotation(d2, h2, bv2)) => { d1 == d2 && h1 == h2 && (*bv1 - *bv2).mag() < 1e-3 } (HelixTranslation(d1, h1), HelixTranslation(d2, h2)) => d1 == d2 && h1 == h2, (GridTranslation(d1, g1), GridTranslation(d2, g2)) => d1 == d2 && g1 == g2, (GridRotation(d1, g1, bv1), GridRotation(d2, g2, bv2)) => { d1 == d2 && g1 == g2 && (*bv1 - *bv2).mag() < 1e-3 } (GridHelixCreation(d1, g1), GridHelixCreation(d2, g2)) => d1 == d2 && g1 == g2, (GridHelixDeletion(d1, g1), GridHelixDeletion(d2, g2)) => d1 == d2 && g1 == g2, (CreateGrid, CreateGrid) => true, (BuildStrand(ts1), BuildStrand(ts2)) => ts1 == ts2, _ => false, } } fn ne(&self, rhs: &Self) -> bool { !self.eq(rhs) } }