1use crate::display::Drawn;
8use crate::notation::Placement;
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13pub enum RepeatExpressionKind {
14 Coda,
16 Segno,
18 Fine,
20 DaCapo,
22 DaCapoAlFine,
24 DaCapoAlCoda,
26 AlSegno,
28 DalSegno,
30 DalSegnoAlFine,
32 DalSegnoAlCoda,
34}
35
36impl RepeatExpressionKind {
37 pub const ALL: [Self; 10] = [
39 Self::Coda,
40 Self::Segno,
41 Self::Fine,
42 Self::DaCapo,
43 Self::DaCapoAlFine,
44 Self::DaCapoAlCoda,
45 Self::AlSegno,
46 Self::DalSegno,
47 Self::DalSegnoAlFine,
48 Self::DalSegnoAlCoda,
49 ];
50
51 pub fn class_name(self) -> &'static str {
53 match self {
54 Self::Coda => "Coda",
55 Self::Segno => "Segno",
56 Self::Fine => "Fine",
57 Self::DaCapo => "DaCapo",
58 Self::DaCapoAlFine => "DaCapoAlFine",
59 Self::DaCapoAlCoda => "DaCapoAlCoda",
60 Self::AlSegno => "AlSegno",
61 Self::DalSegno => "DalSegno",
62 Self::DalSegnoAlFine => "DalSegnoAlFine",
63 Self::DalSegnoAlCoda => "DalSegnoAlCoda",
64 }
65 }
66
67 pub fn from_class_name(name: &str) -> Option<Self> {
69 Self::ALL.into_iter().find(|kind| kind.class_name() == name)
70 }
71
72 pub fn text_alternatives(self) -> &'static [&'static str] {
75 match self {
76 Self::Coda => &["Coda", "to Coda", "al Coda"],
77 Self::Segno => &["Segno"],
78 Self::Fine => &["fine"],
79 Self::DaCapo => &["Da Capo", "D.C."],
80 Self::DaCapoAlFine => &["Da Capo al fine", "D.C. al fine"],
81 Self::DaCapoAlCoda => &["Da Capo al Coda", "D.C. al Coda"],
82 Self::AlSegno => &["al Segno"],
83 Self::DalSegno => &["Dal Segno", "D.S."],
84 Self::DalSegnoAlFine => &["Dal Segno al fine", "D.S. al fine"],
85 Self::DalSegnoAlCoda => &["Dal Segno al Coda", "D.S. al Coda"],
86 }
87 }
88
89 pub fn is_marker(self) -> bool {
93 matches!(self, Self::Coda | Self::Segno | Self::Fine)
94 }
95
96 pub fn justify(self) -> &'static str {
99 match self {
100 Self::Coda | Self::Segno => "center",
101 _ => "right",
102 }
103 }
104
105 pub fn has_symbol(self) -> bool {
108 matches!(self, Self::Coda | Self::Segno)
109 }
110}
111
112#[derive(Clone, Debug, PartialEq, Eq, Hash)]
127#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
128#[must_use]
129pub struct RepeatExpression {
130 kind: RepeatExpressionKind,
131 text: String,
132 use_symbol: bool,
133 #[cfg_attr(feature = "serde", serde(default))]
134 placement: Drawn<Option<Placement>>,
135}
136
137impl RepeatExpression {
138 pub fn new(kind: RepeatExpressionKind) -> Self {
142 Self {
143 kind,
144 text: kind.text_alternatives()[0].to_string(),
145 use_symbol: kind.has_symbol(),
146 placement: Drawn(None),
147 }
148 }
149
150 pub fn with_text(kind: RepeatExpressionKind, text: &str) -> Self {
156 let mut mark = Self::new(kind);
157 let takes_text = !matches!(
158 kind,
159 RepeatExpressionKind::Segno | RepeatExpressionKind::Fine
160 );
161 if takes_text && mark.is_valid_text(text) {
162 mark.text = text.to_string();
163 mark.use_symbol = false;
164 }
165 mark
166 }
167
168 pub fn kind(&self) -> RepeatExpressionKind {
170 self.kind
171 }
172
173 pub fn text(&self) -> &str {
175 &self.text
176 }
177
178 pub fn set_text(&mut self, text: impl Into<String>) {
181 self.text = text.into();
182 }
183
184 pub fn is_valid_text(&self, text: &str) -> bool {
188 let strip = |text: &str| -> String {
189 text.trim()
190 .chars()
191 .filter(|character| *character != ' ' && *character != '.')
192 .flat_map(char::to_lowercase)
193 .collect()
194 };
195 let text = strip(text);
196 self.kind
197 .text_alternatives()
198 .iter()
199 .any(|candidate| strip(candidate) == text)
200 }
201
202 pub fn use_symbol(&self) -> bool {
204 self.use_symbol
205 }
206
207 pub fn set_use_symbol(&mut self, use_symbol: bool) {
210 self.use_symbol = use_symbol;
211 }
212
213 pub fn placement(&self) -> Option<Placement> {
215 self.placement.0
216 }
217
218 pub fn set_placement(&mut self, placement: Option<Placement>) {
220 self.placement.0 = placement;
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227
228 #[test]
229 fn a_mark_takes_only_its_own_words() {
230 let da_capo = RepeatExpression::with_text(RepeatExpressionKind::DaCapo, "D.C.");
232 assert_eq!(da_capo.text(), "D.C.");
233 let wrong = RepeatExpression::with_text(RepeatExpressionKind::DaCapo, "D.S.");
234 assert_eq!(wrong.text(), "Da Capo");
235 assert!(da_capo.is_valid_text("da capo"));
236 }
237
238 #[test]
239 fn a_coda_is_a_sign_until_it_is_given_words() {
240 assert!(RepeatExpression::new(RepeatExpressionKind::Coda).use_symbol());
241 assert!(!RepeatExpression::with_text(RepeatExpressionKind::Coda, "al Coda").use_symbol());
242 assert!(RepeatExpression::new(RepeatExpressionKind::Segno).use_symbol());
243 assert_eq!(RepeatExpressionKind::Fine.justify(), "right");
244 assert_eq!(RepeatExpressionKind::Segno.justify(), "center");
245 }
246}