use std::fmt;
use crate::core::Fraction;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct SpannerAnchor {
pub measure_number: u32,
pub offset: Fraction,
}
impl SpannerAnchor {
pub fn new(measure_number: u32, offset: Fraction) -> Self {
Self {
measure_number,
offset,
}
}
pub fn start_of_measure(measure_number: u32) -> Self {
Self::new(measure_number, Fraction::new(0, 1))
}
}
impl fmt::Display for SpannerAnchor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "m{}+{}", self.measure_number, self.offset)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Spanner {
start: SpannerAnchor,
end: SpannerAnchor,
label: Option<String>,
}
impl Spanner {
pub fn new(start: SpannerAnchor, end: SpannerAnchor) -> Self {
assert!(
end >= start,
"spanner end {end} must not come before its start {start}"
);
Self {
start,
end,
label: None,
}
}
pub fn with_label(start: SpannerAnchor, end: SpannerAnchor, label: impl Into<String>) -> Self {
let mut spanner = Self::new(start, end);
spanner.label = Some(label.into());
spanner
}
pub fn start(&self) -> SpannerAnchor {
self.start
}
pub fn end(&self) -> SpannerAnchor {
self.end
}
pub fn label(&self) -> Option<&str> {
self.label.as_deref()
}
pub fn set_label(&mut self, label: impl Into<String>) {
self.label = Some(label.into());
}
pub fn contains(&self, pos: SpannerAnchor) -> bool {
pos >= self.start && pos <= self.end
}
pub fn is_single_measure(&self) -> bool {
self.start.measure_number == self.end.measure_number
}
pub fn measure_span(&self) -> u32 {
self.end.measure_number - self.start.measure_number + 1
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_spanner_creation_and_contains() {
let start = SpannerAnchor::new(1, Fraction::new(0, 1));
let end = SpannerAnchor::new(2, Fraction::new(1, 1));
let spanner = Spanner::new(start, end);
assert_eq!(spanner.start(), start);
assert_eq!(spanner.end(), end);
assert!(spanner.contains(start));
assert!(spanner.contains(end));
assert!(spanner.contains(SpannerAnchor::new(1, Fraction::new(3, 1))));
assert!(!spanner.contains(SpannerAnchor::new(3, Fraction::new(0, 1))));
}
#[test]
fn test_spanner_measure_span() {
let spanner = Spanner::new(
SpannerAnchor::start_of_measure(3),
SpannerAnchor::start_of_measure(5),
);
assert!(!spanner.is_single_measure());
assert_eq!(spanner.measure_span(), 3);
let single = Spanner::new(
SpannerAnchor::new(1, Fraction::new(0, 1)),
SpannerAnchor::new(1, Fraction::new(2, 1)),
);
assert!(single.is_single_measure());
assert_eq!(single.measure_span(), 1);
}
#[test]
fn test_spanner_with_label() {
let spanner = Spanner::with_label(
SpannerAnchor::start_of_measure(1),
SpannerAnchor::start_of_measure(1),
"crescendo",
);
assert_eq!(spanner.label(), Some("crescendo"));
}
#[test]
#[should_panic(expected = "must not come before its start")]
fn test_spanner_rejects_end_before_start() {
Spanner::new(
SpannerAnchor::start_of_measure(5),
SpannerAnchor::start_of_measure(1),
);
}
}