1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
use super::UndoAction;
use crate::document::Document;
use crate::gpu::blend_mode::BlendModeRegistration;
use crate::gpu::params::ParamValue;
use crate::layer::{Layer, LayerId, LayerNode};
use std::collections::{HashMap, HashSet};
/// A layer property value that can be saved and restored.
#[derive(Clone)]
pub enum Property {
Opacity(f32),
/// Held as a registry reference so undo records the *same* identity the
/// document carries — there is no enum copy that could drift.
BlendMode(&'static BlendModeRegistration),
Visible(bool),
Name(String),
Passthrough(bool),
Collapsed(bool),
/// Full parameter vector of a void layer. Same shape the wire format
/// uses; replaces in bulk because a void's params are dependent on its
/// `void_type` and only meaningful as a complete schema-aligned vector.
/// Coalescing on the `same_kind` discriminant collapses a slider drag
/// (multiple `VoidParams` edits in a row) into one undo step, matching
/// how opacity behaves.
VoidParams(Vec<ParamValue>),
/// A void layer's user transform (gizmo-edited pan / scale / rotate).
/// Coalesces on `same_kind` so a whole gizmo drag is one undo step,
/// exactly like `VoidParams`. The GPU re-sync happens in
/// `sync_compositor_layers` after the doc is restored.
Transform(crate::transform::Transform),
}
impl Property {
/// Returns true if both values are the same property kind (e.g. both Opacity).
pub fn same_kind(&self, other: &Property) -> bool {
std::mem::discriminant(self) == std::mem::discriminant(other)
}
/// Apply this property value to the layer/group in the document.
fn apply(&self, doc: &mut Document, layer_id: LayerId) {
let node = match doc.find_node_mut(layer_id) {
Some(n) => n,
None => return,
};
match self {
Property::Opacity(v) => node.blend_mut().opacity = *v,
Property::BlendMode(v) => node.blend_mut().blend_mode = *v,
Property::Visible(v) => node.common_mut().visible = *v,
Property::Name(v) => node.common_mut().name = v.clone(),
Property::Passthrough(v) => {
if let LayerNode::Group(g) = node {
g.passthrough = *v;
}
}
Property::Collapsed(v) => {
if let LayerNode::Group(g) = node {
g.collapsed = *v;
}
}
Property::VoidParams(values) => {
if let LayerNode::Layer(Layer::Void(v)) = node {
v.params = values.clone();
}
}
Property::Transform(t) => {
if let LayerNode::Layer(Layer::Void(v)) = node {
v.transform = *t;
}
}
}
}
}
/// Undo action for a property change on a layer or group.
pub struct PropertyAction {
layer_id: LayerId,
old_value: Property,
new_value: Property,
}
impl PropertyAction {
pub fn new(layer_id: LayerId, old_value: Property, new_value: Property) -> Self {
PropertyAction {
layer_id,
old_value,
new_value,
}
}
/// Try to coalesce another PropertyAction into this one.
/// Succeeds if both target the same layer and same property kind,
/// in which case we keep our `old_value` and take their `new_value`.
pub fn try_coalesce(&mut self, other: &PropertyAction) -> bool {
if self.layer_id == other.layer_id && self.new_value.same_kind(&other.new_value) {
self.new_value = other.new_value.clone();
true
} else {
false
}
}
}
impl UndoAction for PropertyAction {
fn undo(&mut self, doc: &mut Document) -> HashMap<LayerId, HashSet<(i32, i32)>> {
self.old_value.apply(doc, self.layer_id);
HashMap::new()
}
fn redo(&mut self, doc: &mut Document) -> HashMap<LayerId, HashSet<(i32, i32)>> {
self.new_value.apply(doc, self.layer_id);
HashMap::new()
}
fn try_coalesce_property(&mut self, other: &PropertyAction) -> bool {
self.try_coalesce(other)
}
}