use crate::edit::{Edit, EditError, NodeTree, PropertyPath, Transaction, Value};
use crate::figure::Figure;
use crate::ids::NodeId;
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Overlay {
entries: Vec<OverlayEntry>,
conflicts: Vec<Conflict>,
undo: Vec<Vec<OverlayEntry>>,
redo: Vec<Vec<OverlayEntry>>,
open_step: Option<Vec<OverlayEntry>>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct OverlayEntry {
pub node: NodeId,
pub path: PropertyPath,
pub value: Value,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Conflict {
pub node: NodeId,
pub overlay_path: PropertyPath,
pub source_path: PropertyPath,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Resolution {
UseSource,
KeepMine,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Composition {
pub figure: Figure,
pub dropped: Vec<Dropped>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Dropped {
pub entry: OverlayEntry,
pub reason: EditError,
}
impl Overlay {
pub fn new() -> Self {
Self::default()
}
pub fn entries(&self) -> &[OverlayEntry] {
&self.entries
}
pub fn record(&mut self, transaction: &Transaction) -> Result<(), EditError> {
for (index, edit) in transaction.edits.iter().enumerate() {
if !matches!(edit, Edit::Set { .. }) {
return Err(EditError::NotASet { edit: Some(index) });
}
}
let before = self.entries.clone();
for edit in &transaction.edits {
let Edit::Set { node, path, value } = edit else {
unreachable!("every edit was checked to be a set");
};
self.entries
.retain(|entry| !(entry.node == *node && path.contains(&entry.path)));
self.entries.push(OverlayEntry {
node: *node,
path: path.clone(),
value: value.clone(),
});
}
self.commit(before);
Ok(())
}
pub fn begin_step(&mut self) {
if self.open_step.is_none() {
self.open_step = Some(self.entries.clone());
}
}
pub fn end_step(&mut self) {
if let Some(before) = self.open_step.take() {
self.commit(before);
}
}
fn commit(&mut self, before: Vec<OverlayEntry>) {
if self.open_step.is_none() && before != self.entries {
self.undo.push(before);
self.redo.clear();
}
}
pub fn compose(&self, source: &Figure) -> Composition {
let mut figure = source.clone();
let mut dropped = Vec::new();
for entry in &self.entries {
let transaction = Transaction {
edits: vec![Edit::Set {
node: entry.node,
path: entry.path.clone(),
value: entry.value.clone(),
}],
};
if let Err(reason) = figure.apply(&transaction) {
dropped.push(Dropped {
entry: entry.clone(),
reason: reason.at_edit(None),
});
}
}
Composition { figure, dropped }
}
pub fn discard(&mut self, dropped: &[Dropped]) {
self.entries
.retain(|entry| !dropped.iter().any(|d| d.entry == *entry));
if self.open_step.is_none() && self.undo.last() == Some(&self.entries) {
self.undo.pop();
}
}
pub fn reconcile(&mut self, source: &Figure, transaction: &Transaction) -> Vec<Conflict> {
let mut tree = NodeTree::of(source);
let mut raised: Vec<Conflict> = Vec::new();
for edit in &transaction.edits {
match edit {
Edit::Set { node, path, .. } => {
for entry in &self.entries {
if entry.node != *node || !entry.path.overlaps(path) {
continue;
}
let conflict = Conflict {
node: *node,
overlay_path: entry.path.clone(),
source_path: path.clone(),
};
if !raised.contains(&conflict) {
raised.push(conflict);
}
}
}
Edit::Remove { node } => {
let removed = tree.subtree(*node);
let gone = |node: &NodeId| removed.contains(node);
self.entries.retain(|entry| !gone(&entry.node));
self.conflicts.retain(|conflict| !gone(&conflict.node));
raised.retain(|conflict| !gone(&conflict.node));
let steps = self.undo.iter_mut().chain(self.redo.iter_mut());
for step in steps.chain(self.open_step.iter_mut()) {
step.retain(|entry| !gone(&entry.node));
}
tree.apply(edit);
}
_ => tree.apply(edit),
}
}
for conflict in &raised {
if !self.conflicts.contains(conflict) {
self.conflicts.push(conflict.clone());
}
}
raised
}
pub fn conflicts(&self) -> &[Conflict] {
&self.conflicts
}
pub fn resolve(&mut self, conflict: &Conflict, resolution: Resolution) -> bool {
if !self.conflicts.contains(conflict) {
return false;
}
self.conflicts.retain(|other| {
other.node != conflict.node || other.overlay_path != conflict.overlay_path
});
if resolution == Resolution::UseSource {
self.entries
.retain(|entry| entry.node != conflict.node || entry.path != conflict.overlay_path);
}
true
}
pub fn revert(&mut self, node: NodeId, path: &PropertyPath) -> bool {
let before = self.entries.clone();
self.entries
.retain(|entry| !(entry.node == node && entry.path == *path));
if self.entries.len() == before.len() {
return false;
}
self.conflicts
.retain(|conflict| !(conflict.node == node && conflict.overlay_path == *path));
self.commit(before);
true
}
pub fn clear(&mut self) -> bool {
if *self == Self::default() {
return false;
}
*self = Self::default();
true
}
pub fn reset_view(&mut self, axes: NodeId) {
self.reset(|entry| entry.node == axes);
}
pub fn reset_all_views(&mut self) {
self.reset(|_| true);
}
fn reset(&mut self, node: impl Fn(&OverlayEntry) -> bool) {
let before = self.entries.clone();
self.entries
.retain(|entry| !(node(entry) && is_view_property(&entry.path)));
self.commit(before);
}
pub fn undo(&mut self) -> bool {
match self.undo.pop() {
None => false,
Some(entries) => {
self.redo
.push(std::mem::replace(&mut self.entries, entries));
true
}
}
}
pub fn redo(&mut self) -> bool {
match self.redo.pop() {
None => false,
Some(entries) => {
self.undo
.push(std::mem::replace(&mut self.entries, entries));
true
}
}
}
pub fn can_undo(&self) -> bool {
!self.undo.is_empty()
}
pub fn can_redo(&self) -> bool {
!self.redo.is_empty()
}
pub fn to_transaction(&self) -> Transaction {
Transaction {
edits: self
.entries
.iter()
.map(|entry| Edit::Set {
node: entry.node,
path: entry.path.clone(),
value: entry.value.clone(),
})
.collect(),
}
}
}
fn is_view_property(path: &PropertyPath) -> bool {
[
PropertyPath::of(&["x", "limits"]),
PropertyPath::of(&["y", "limits"]),
PropertyPath::of(&["z", "limits"]),
PropertyPath::of(&["projection", "view3d"]),
]
.iter()
.any(|view| view.contains(path))
}