1use std::collections::BTreeMap;
9
10use serde::{Deserialize, Serialize};
11
12use crate::document::{
13 BlockLabel, GridPos, GridRect, LinearDistance, Lock, PinSide, PinType, TagVisibility, Waypoint,
14};
15use crate::log::id::{AssetId, ElementId};
16
17#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)]
20pub enum ElementKind {
21 Block,
22 Pin,
23 Route,
24 Text,
25 Comment,
26 Image,
27 Icon,
31 RouteLabel,
32}
33
34#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
37pub struct TagLabel {
38 pub text: String,
39 pub visibility: TagVisibility,
40}
41
42#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
44pub struct PinPlacement {
45 pub side: PinSide,
46 pub offset: u32,
47}
48
49macro_rules! registers {
64 ($( $(#[$doc:meta])* $name:ident($ty:ty) = $code:literal ),+ $(,)?) => {
65 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)]
67 #[repr(u16)]
68 pub enum PropTag {
69 $( $(#[$doc])* $name = $code, )+
70 }
71
72 impl PropTag {
73 pub const fn code(self) -> u16 {
74 self as u16
75 }
76
77 pub const fn from_code(code: u16) -> Option<Self> {
78 match code {
79 $( $code => Some(Self::$name), )+
80 _ => None,
81 }
82 }
83 }
84
85 #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
91 pub enum PropWrite {
92 $( $(#[$doc])* $name($ty), )+
93 }
94
95 impl PropWrite {
96 pub fn tag(&self) -> PropTag {
97 match self {
98 $( Self::$name(_) => PropTag::$name, )+
99 }
100 }
101 }
102
103 #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
109 pub enum PropChange {
110 $( $(#[$doc])* $name { old: Option<$ty>, new: Option<$ty> }, )+
111 }
112
113 impl PropChange {
114 pub fn tag(&self) -> PropTag {
115 match self {
116 $( Self::$name { .. } => PropTag::$name, )+
117 }
118 }
119
120 pub fn old(&self) -> Option<PropWrite> {
124 match self {
125 $( Self::$name { old, .. } => old.clone().map(PropWrite::$name), )+
126 }
127 }
128
129 pub fn new_value(&self) -> Option<PropWrite> {
131 match self {
132 $( Self::$name { new, .. } => new.clone().map(PropWrite::$name), )+
133 }
134 }
135
136 pub fn inverted(&self) -> Self {
138 match self {
139 $( Self::$name { old, new } => Self::$name {
140 old: new.clone(),
141 new: old.clone(),
142 }, )+
143 }
144 }
145 }
146 };
147}
148
149registers! {
150 Parent(ElementId) = 1,
153 Rect(GridRect) = 2,
155 Anchor(GridPos) = 3,
158 Title(BlockLabel) = 4,
159 TypeLabel(BlockLabel) = 5,
160 Name(String) = 6,
162 TypeName(String) = 7,
164 Tag(TagLabel) = 8,
165 Placement(PinPlacement) = 9,
166 PinKind(PinType) = 10,
167 PortOrientation(PinSide) = 11,
168 RouteStart(ElementId) = 12,
172 RouteFinish(ElementId) = 13,
173 Waypoints(Vec<Waypoint>) = 14,
176 Along(LinearDistance) = 15,
178 Body(String) = 16,
180 Accent(u8) = 17,
181 Locked(Lock) = 18,
182 Asset(AssetId) = 19,
184 DocumentName(String) = 20,
185}
186
187impl PropWrite {
188 pub fn reference(&self) -> Option<ElementId> {
191 match self {
192 Self::Parent(id) | Self::RouteStart(id) | Self::RouteFinish(id) => Some(*id),
193 _ => None,
194 }
195 }
196}
197
198#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize)]
202#[serde(into = "Vec<PropWrite>", try_from = "Vec<PropWrite>")]
203pub struct PropSet(BTreeMap<PropTag, PropWrite>);
204
205impl From<PropSet> for Vec<PropWrite> {
206 fn from(set: PropSet) -> Self {
207 set.0.into_values().collect()
208 }
209}
210
211impl TryFrom<Vec<PropWrite>> for PropSet {
212 type Error = &'static str;
213
214 fn try_from(writes: Vec<PropWrite>) -> Result<Self, Self::Error> {
218 let mut set = BTreeMap::new();
219 for write in writes {
220 if set.insert(write.tag(), write).is_some() {
221 return Err("a create names one register twice");
222 }
223 }
224 Ok(Self(set))
225 }
226}
227
228impl PropSet {
229 pub fn new() -> Self {
230 Self::default()
231 }
232
233 pub fn with(mut self, write: PropWrite) -> Self {
235 self.insert(write);
236 self
237 }
238
239 pub fn insert(&mut self, write: PropWrite) -> Option<PropWrite> {
240 self.0.insert(write.tag(), write)
241 }
242
243 pub fn get(&self, tag: PropTag) -> Option<&PropWrite> {
244 self.0.get(&tag)
245 }
246
247 pub fn len(&self) -> usize {
248 self.0.len()
249 }
250
251 pub fn is_empty(&self) -> bool {
252 self.0.is_empty()
253 }
254
255 pub fn iter(&self) -> impl Iterator<Item = &PropWrite> {
257 self.0.values()
258 }
259}
260
261impl FromIterator<PropWrite> for PropSet {
262 fn from_iter<I: IntoIterator<Item = PropWrite>>(iter: I) -> Self {
263 Self(iter.into_iter().map(|w| (w.tag(), w)).collect())
264 }
265}
266
267#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
275pub enum Command {
276 Create {
277 id: ElementId,
278 kind: ElementKind,
279 parent: ElementId,
280 init: PropSet,
281 },
282 SetProp {
283 id: ElementId,
284 change: PropChange,
285 },
286 Delete {
289 id: ElementId,
290 },
291 Restore {
292 id: ElementId,
293 },
294}
295
296impl Command {
297 pub fn target(&self) -> ElementId {
298 match self {
299 Self::Create { id, .. }
300 | Self::SetProp { id, .. }
301 | Self::Delete { id }
302 | Self::Restore { id } => *id,
303 }
304 }
305
306 pub fn inverted(&self) -> Self {
309 match self {
310 Self::Delete { id } => Self::Restore { id: *id },
311 Self::Create { id, .. } | Self::Restore { id } => Self::Delete { id: *id },
312 Self::SetProp { id, change } => Self::SetProp {
313 id: *id,
314 change: change.inverted(),
315 },
316 }
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323
324 #[test]
329 fn a_property_edit_names_one_register_by_construction() {
330 let change = PropChange::Accent {
331 old: Some(1),
332 new: Some(3),
333 };
334 assert_eq!(change.tag(), PropTag::Accent);
335 assert_eq!(change.old(), Some(PropWrite::Accent(1)));
336 assert_eq!(change.new_value(), Some(PropWrite::Accent(3)));
337 }
338
339 #[test]
340 fn inverting_a_prop_change_swaps_its_sides() {
341 let change = PropChange::Accent {
342 old: Some(1),
343 new: Some(3),
344 };
345 let back = change.inverted();
346 assert_eq!(back.old(), change.new_value());
347 assert_eq!(back.new_value(), change.old());
348 assert_eq!(back.inverted(), change);
349 }
350
351 #[test]
355 fn setting_a_first_value_inverts_into_clearing_it() {
356 let set = PropChange::Accent {
357 old: None,
358 new: Some(3),
359 };
360 let undo = set.inverted();
361
362 assert_eq!(undo.new_value(), None);
363 assert_eq!(undo.old(), Some(PropWrite::Accent(3)));
364 assert_eq!(
365 undo.tag(),
366 PropTag::Accent,
367 "a cleared register still names the register it clears"
368 );
369 assert_eq!(undo.inverted(), set);
370 }
371
372 #[test]
373 fn a_prop_set_holds_one_write_per_register() {
374 let set: PropSet = [
375 PropWrite::Name("first".into()),
376 PropWrite::Accent(1),
377 PropWrite::Name("second".into()),
378 ]
379 .into_iter()
380 .collect();
381
382 assert_eq!(
383 set.len(),
384 2,
385 "the duplicate register must not be kept twice"
386 );
387 assert_eq!(
388 set.get(PropTag::Name),
389 Some(&PropWrite::Name("second".into())),
390 "the later write should win"
391 );
392 }
393
394 #[test]
397 fn a_prop_set_iterates_in_tag_order_whatever_the_build_order() {
398 let forwards: PropSet = [PropWrite::Parent(ElementId::DOCUMENT), PropWrite::Accent(1)]
399 .into_iter()
400 .collect();
401 let backwards: PropSet = [PropWrite::Accent(1), PropWrite::Parent(ElementId::DOCUMENT)]
402 .into_iter()
403 .collect();
404
405 let tags = |s: &PropSet| s.iter().map(PropWrite::tag).collect::<Vec<_>>();
406 assert_eq!(tags(&forwards), tags(&backwards));
407 assert_eq!(tags(&forwards), vec![PropTag::Parent, PropTag::Accent]);
408 }
409
410 #[test]
414 fn every_tag_round_trips_through_its_code() {
415 for code in 1..=20u16 {
416 let tag = PropTag::from_code(code).expect("codes 1..=20 are all assigned");
417 assert_eq!(tag.code(), code);
418 }
419 assert!(PropTag::from_code(0).is_none());
420 assert!(PropTag::from_code(21).is_none());
421 }
422
423 #[test]
424 fn creates_and_deletes_invert_into_each_other() {
425 let id = ElementId::new();
426 let create = Command::Create {
427 id,
428 kind: ElementKind::Block,
429 parent: ElementId::DOCUMENT,
430 init: PropSet::new(),
431 };
432 assert_eq!(create.inverted(), Command::Delete { id });
433 assert_eq!(
434 Command::Delete { id }.inverted(),
435 Command::Restore { id },
436 "undoing a delete restores rather than recreating, so identity survives"
437 );
438 }
439}