use std::ops::Range;
use crate::piet::{Color, FontFamily, FontStyle, FontWeight, TextAttribute as PietAttr};
use crate::{Command, Env, FontDescriptor, KeyOrValue};
use super::EnvUpdateCtx;
#[derive(Debug, Clone)]
pub struct Link {
pub range: Range<usize>,
pub command: Command,
}
#[derive(Debug, Clone, Default)]
pub struct AttributeSpans {
family: SpanSet<FontFamily>,
size: SpanSet<KeyOrValue<f64>>,
weight: SpanSet<FontWeight>,
fg_color: SpanSet<KeyOrValue<Color>>,
style: SpanSet<FontStyle>,
underline: SpanSet<bool>,
strikethrough: SpanSet<bool>,
font_descriptor: SpanSet<KeyOrValue<FontDescriptor>>,
}
#[derive(Debug, Clone)]
struct SpanSet<T> {
spans: Vec<Span<T>>,
}
#[derive(Debug, Clone, PartialEq)]
struct Span<T> {
range: Range<usize>,
attr: T,
}
#[derive(Debug, Clone)]
pub enum Attribute {
FontFamily(FontFamily),
FontSize(KeyOrValue<f64>),
Weight(FontWeight),
TextColor(KeyOrValue<Color>),
Style(FontStyle),
Underline(bool),
Strikethrough(bool),
Descriptor(KeyOrValue<FontDescriptor>),
}
impl Link {
pub fn new(range: Range<usize>, command: Command) -> Self {
Self { range, command }
}
pub fn range(&self) -> Range<usize> {
self.range.clone()
}
}
impl AttributeSpans {
pub fn new() -> Self {
Default::default()
}
pub fn add(&mut self, range: Range<usize>, attr: Attribute) {
match attr {
Attribute::FontFamily(attr) => self.family.add(Span::new(range, attr)),
Attribute::FontSize(attr) => self.size.add(Span::new(range, attr)),
Attribute::Weight(attr) => self.weight.add(Span::new(range, attr)),
Attribute::TextColor(attr) => self.fg_color.add(Span::new(range, attr)),
Attribute::Style(attr) => self.style.add(Span::new(range, attr)),
Attribute::Underline(attr) => self.underline.add(Span::new(range, attr)),
Attribute::Strikethrough(attr) => self.strikethrough.add(Span::new(range, attr)),
Attribute::Descriptor(attr) => self.font_descriptor.add(Span::new(range, attr)),
}
}
pub(crate) fn to_piet_attrs(&self, env: &Env) -> Vec<(Range<usize>, PietAttr)> {
let mut items = Vec::new();
for Span { range, attr } in self.font_descriptor.iter() {
let font = attr.resolve(env);
items.push((range.clone(), PietAttr::FontFamily(font.family)));
items.push((range.clone(), PietAttr::FontSize(font.size)));
items.push((range.clone(), PietAttr::Weight(font.weight)));
items.push((range.clone(), PietAttr::Style(font.style)));
}
items.extend(
self.family
.iter()
.map(|s| (s.range.clone(), PietAttr::FontFamily(s.attr.clone()))),
);
items.extend(
self.size
.iter()
.map(|s| (s.range.clone(), PietAttr::FontSize(s.attr.resolve(env)))),
);
items.extend(
self.weight
.iter()
.map(|s| (s.range.clone(), PietAttr::Weight(s.attr))),
);
items.extend(
self.fg_color
.iter()
.map(|s| (s.range.clone(), PietAttr::TextColor(s.attr.resolve(env)))),
);
items.extend(
self.style
.iter()
.map(|s| (s.range.clone(), PietAttr::Style(s.attr))),
);
items.extend(
self.underline
.iter()
.map(|s| (s.range.clone(), PietAttr::Underline(s.attr))),
);
items.extend(
self.strikethrough
.iter()
.map(|s| (s.range.clone(), PietAttr::Strikethrough(s.attr))),
);
items.sort_by(|a, b| a.0.start.cmp(&b.0.start));
items
}
pub(crate) fn env_update(&self, ctx: &EnvUpdateCtx) -> bool {
self.size
.iter()
.any(|span_attr| ctx.env_key_changed(&span_attr.attr))
|| self
.fg_color
.iter()
.any(|span_attr| ctx.env_key_changed(&span_attr.attr))
|| self
.font_descriptor
.iter()
.any(|span_attr| ctx.env_key_changed(&span_attr.attr))
}
}
impl<T: Clone> SpanSet<T> {
fn iter(&self) -> impl Iterator<Item = &Span<T>> {
self.spans.iter()
}
fn add(&mut self, span: Span<T>) {
let span_start = span.range.start;
let span_end = span.range.end;
let insert_idx = self
.spans
.iter()
.position(|x| x.range.start >= span.range.start)
.unwrap_or(self.spans.len());
let mut prev_remainder = None;
if insert_idx > 0 {
let before = self.spans.get_mut(insert_idx - 1).unwrap();
if before.range.end > span_end {
let mut remainder = before.clone();
remainder.range.start = span_end;
prev_remainder = Some(remainder);
}
before.range.end = before.range.end.min(span_start);
}
self.spans.insert(insert_idx, span);
if let Some(remainder) = prev_remainder.take() {
self.spans.insert(insert_idx + 1, remainder);
}
for after in self.spans.iter_mut().skip(insert_idx + 1) {
after.range.start = after.range.start.max(span_end);
after.range.end = after.range.end.max(span_end);
}
self.spans.retain(|span| !span.is_empty());
}
#[allow(dead_code, clippy::branches_sharing_code)]
fn edit(&mut self, changed: Range<usize>, new_len: usize) {
let old_len = changed.len();
let mut to_insert = None;
for (idx, Span { range, attr }) in self.spans.iter_mut().enumerate() {
if range.end <= changed.start {
continue;
} else if range.start < changed.start {
if range.end <= changed.end {
range.end = changed.start;
} else {
let new_start = changed.start + new_len;
let new_end = range.end - old_len + new_len;
let new_span = Span::new(new_start..new_end, attr.clone());
to_insert = Some((idx + 1, new_span));
range.end = changed.start;
}
} else if range.start < changed.end {
range.start = changed.start + new_len;
if range.end <= changed.end {
range.end = changed.start + new_len;
} else {
range.end -= old_len;
range.end += new_len;
}
} else {
range.start -= old_len;
range.start += new_len;
range.end -= old_len;
range.end += new_len;
}
}
if let Some((idx, span)) = to_insert.take() {
self.spans.insert(idx, span);
}
self.spans.retain(|span| !span.is_empty());
}
}
impl<T> Span<T> {
fn new(range: Range<usize>, attr: T) -> Self {
Span { range, attr }
}
fn is_empty(&self) -> bool {
self.range.end <= self.range.start
}
}
impl Attribute {
pub fn size(size: impl Into<KeyOrValue<f64>>) -> Self {
Attribute::FontSize(size.into())
}
pub fn text_color(color: impl Into<KeyOrValue<Color>>) -> Self {
Attribute::TextColor(color.into())
}
pub fn font_family(family: FontFamily) -> Self {
Attribute::FontFamily(family)
}
pub fn weight(weight: FontWeight) -> Self {
Attribute::Weight(weight)
}
pub fn style(style: FontStyle) -> Self {
Attribute::Style(style)
}
pub fn underline(underline: bool) -> Self {
Attribute::Underline(underline)
}
pub fn strikethrough(strikethrough: bool) -> Self {
Attribute::Strikethrough(strikethrough)
}
pub fn font_descriptor(font: impl Into<KeyOrValue<FontDescriptor>>) -> Self {
Attribute::Descriptor(font.into())
}
}
impl<T> Default for SpanSet<T> {
fn default() -> Self {
SpanSet { spans: Vec::new() }
}
}
#[cfg(test)]
mod tests {
use super::*;
use test_log::test;
#[test]
fn smoke_test_spans() {
let mut spans = SpanSet::<u32>::default();
spans.add(Span::new(2..10, 1));
spans.add(Span::new(3..6, 2));
assert_eq!(
&spans.spans,
&vec![Span::new(2..3, 1), Span::new(3..6, 2), Span::new(6..10, 1)]
);
spans.add(Span::new(0..12, 3));
assert_eq!(&spans.spans, &vec![Span::new(0..12, 3)]);
spans.add(Span::new(5..20, 4));
assert_eq!(&spans.spans, &vec![Span::new(0..5, 3), Span::new(5..20, 4)]);
}
#[test]
fn edit_spans() {
let mut spans = SpanSet::<u32>::default();
spans.add(Span::new(0..2, 1));
spans.add(Span::new(8..12, 2));
spans.add(Span::new(13..16, 3));
spans.add(Span::new(20..22, 4));
let mut deletion = spans.clone();
deletion.edit(6..14, 0);
assert_eq!(
&deletion.spans,
&vec![Span::new(0..2, 1), Span::new(6..8, 3), Span::new(12..14, 4)]
);
spans.edit(10..10, 2);
assert_eq!(
&spans.spans,
&vec![
Span::new(0..2, 1),
Span::new(8..10, 2),
Span::new(12..14, 2),
Span::new(15..18, 3),
Span::new(22..24, 4),
]
);
}
}