1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
5pub enum ChangeScope {
6 Global,
8 Part(usize),
10 Measures {
12 part: usize,
13 staff: usize,
14 start: usize,
15 end: usize,
16 },
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct ChangeHint {
25 pub scope: ChangeScope,
26 pub layout_dirty: bool,
29 pub playback_dirty: bool,
31}
32
33impl ChangeHint {
34 pub fn merge(self, other: ChangeHint) -> ChangeHint {
36 ChangeHint {
37 scope: merge_scope(self.scope, other.scope),
38 layout_dirty: self.layout_dirty || other.layout_dirty,
39 playback_dirty: self.playback_dirty || other.playback_dirty,
40 }
41 }
42}
43
44fn merge_scope(a: ChangeScope, b: ChangeScope) -> ChangeScope {
45 match (a, b) {
46 (ChangeScope::Global, _) | (_, ChangeScope::Global) => ChangeScope::Global,
47 (ChangeScope::Part(pa), ChangeScope::Part(pb)) if pa == pb => ChangeScope::Part(pa),
48 (ChangeScope::Part(_), ChangeScope::Part(_)) => ChangeScope::Global,
49 (
50 ChangeScope::Measures {
51 part: pa,
52 staff: sa,
53 start: s1,
54 end: e1,
55 },
56 ChangeScope::Measures {
57 part: pb,
58 staff: sb,
59 start: s2,
60 end: e2,
61 },
62 ) if pa == pb && sa == sb => ChangeScope::Measures {
63 part: pa,
64 staff: sa,
65 start: s1.min(s2),
66 end: e1.max(e2),
67 },
68 _ => ChangeScope::Global,
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 fn hint(scope: ChangeScope, layout: bool, playback: bool) -> ChangeHint {
77 ChangeHint {
78 scope,
79 layout_dirty: layout,
80 playback_dirty: playback,
81 }
82 }
83
84 #[test]
85 fn merge_global_wins() {
86 let a = hint(ChangeScope::Global, false, true);
87 let b = hint(ChangeScope::Part(0), true, false);
88 let m = a.merge(b);
89 assert_eq!(m.scope, ChangeScope::Global);
90 assert!(m.layout_dirty);
91 assert!(m.playback_dirty);
92 }
93
94 #[test]
95 fn merge_same_part_stays_part() {
96 let a = hint(ChangeScope::Part(1), false, true);
97 let b = hint(ChangeScope::Part(1), true, false);
98 let m = a.merge(b);
99 assert_eq!(m.scope, ChangeScope::Part(1));
100 }
101
102 #[test]
103 fn merge_different_parts_becomes_global() {
104 let a = hint(ChangeScope::Part(0), false, false);
105 let b = hint(ChangeScope::Part(1), false, false);
106 let m = a.merge(b);
107 assert_eq!(m.scope, ChangeScope::Global);
108 }
109
110 #[test]
111 fn merge_measures_same_staff_extends_range() {
112 let a = hint(
113 ChangeScope::Measures {
114 part: 0,
115 staff: 0,
116 start: 2,
117 end: 4,
118 },
119 false,
120 true,
121 );
122 let b = hint(
123 ChangeScope::Measures {
124 part: 0,
125 staff: 0,
126 start: 1,
127 end: 3,
128 },
129 false,
130 true,
131 );
132 let m = a.merge(b);
133 assert_eq!(
134 m.scope,
135 ChangeScope::Measures {
136 part: 0,
137 staff: 0,
138 start: 1,
139 end: 4
140 }
141 );
142 }
143
144 #[test]
145 fn merge_measures_different_staff_becomes_global() {
146 let a = hint(
147 ChangeScope::Measures {
148 part: 0,
149 staff: 0,
150 start: 0,
151 end: 1,
152 },
153 false,
154 true,
155 );
156 let b = hint(
157 ChangeScope::Measures {
158 part: 0,
159 staff: 1,
160 start: 0,
161 end: 1,
162 },
163 false,
164 true,
165 );
166 let m = a.merge(b);
167 assert_eq!(m.scope, ChangeScope::Global);
168 }
169}