use std::ops::{Range, RangeBounds};
use std::sync::Arc;
use super::attribute::Link;
use super::{Attribute, AttributeSpans, EnvUpdateCtx, TextStorage};
use crate::piet::{
util, Color, FontFamily, FontStyle, FontWeight, PietTextLayoutBuilder, TextLayoutBuilder,
TextStorage as PietTextStorage,
};
use crate::{ArcStr, Command, Data, Env, FontDescriptor, KeyOrValue};
#[derive(Clone, Debug, Data)]
pub struct RichText {
buffer: ArcStr,
attrs: Arc<AttributeSpans>,
links: Arc<[Link]>,
}
impl RichText {
pub fn new(buffer: ArcStr) -> Self {
RichText::new_with_attributes(buffer, Default::default())
}
pub fn new_with_attributes(buffer: ArcStr, attributes: AttributeSpans) -> Self {
RichText {
buffer,
attrs: Arc::new(attributes),
links: Arc::new([]),
}
}
pub fn with_attribute(mut self, range: impl RangeBounds<usize>, attr: Attribute) -> Self {
self.add_attribute(range, attr);
self
}
pub fn len(&self) -> usize {
self.buffer.len()
}
pub fn is_empty(&self) -> bool {
self.buffer.is_empty()
}
pub fn add_attribute(&mut self, range: impl RangeBounds<usize>, attr: Attribute) {
let range = util::resolve_range(range, self.buffer.len());
Arc::make_mut(&mut self.attrs).add(range, attr);
}
}
impl PietTextStorage for RichText {
fn as_str(&self) -> &str {
self.buffer.as_str()
}
}
impl TextStorage for RichText {
fn add_attributes(
&self,
mut builder: PietTextLayoutBuilder,
env: &Env,
) -> PietTextLayoutBuilder {
for (range, attr) in self.attrs.to_piet_attrs(env) {
builder = builder.range_attribute(range, attr);
}
builder
}
fn env_update(&self, ctx: &EnvUpdateCtx) -> bool {
self.attrs.env_update(ctx)
}
fn links(&self) -> &[Link] {
&self.links
}
}
#[derive(Default)]
pub struct RichTextBuilder {
buffer: String,
attrs: AttributeSpans,
links: Vec<Link>,
}
impl RichTextBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, string: &str) -> AttributesAdder {
let range = self.buffer.len()..(self.buffer.len() + string.len());
self.buffer.push_str(string);
self.add_attributes_for_range(range)
}
#[doc(hidden)]
pub fn write_fmt(&mut self, fmt: std::fmt::Arguments<'_>) -> AttributesAdder {
use std::fmt::Write;
let start = self.buffer.len();
self.buffer
.write_fmt(fmt)
.expect("a formatting trait implementation returned an error");
self.add_attributes_for_range(start..self.buffer.len())
}
pub fn add_attributes_for_range(&mut self, range: impl RangeBounds<usize>) -> AttributesAdder {
let range = util::resolve_range(range, self.buffer.len());
AttributesAdder {
rich_text_builder: self,
range,
}
}
pub fn build(self) -> RichText {
RichText {
buffer: self.buffer.into(),
attrs: self.attrs.into(),
links: self.links.into(),
}
}
}
pub struct AttributesAdder<'a> {
rich_text_builder: &'a mut RichTextBuilder,
range: Range<usize>,
}
impl AttributesAdder<'_> {
pub fn add_attr(&mut self, attr: Attribute) -> &mut Self {
self.rich_text_builder.attrs.add(self.range.clone(), attr);
self
}
pub fn size(&mut self, size: impl Into<KeyOrValue<f64>>) -> &mut Self {
self.add_attr(Attribute::size(size));
self
}
pub fn text_color(&mut self, color: impl Into<KeyOrValue<Color>>) -> &mut Self {
self.add_attr(Attribute::text_color(color));
self
}
pub fn font_family(&mut self, family: FontFamily) -> &mut Self {
self.add_attr(Attribute::font_family(family));
self
}
pub fn weight(&mut self, weight: FontWeight) -> &mut Self {
self.add_attr(Attribute::weight(weight));
self
}
pub fn style(&mut self, style: FontStyle) -> &mut Self {
self.add_attr(Attribute::style(style));
self
}
pub fn underline(&mut self, underline: bool) -> &mut Self {
self.add_attr(Attribute::underline(underline));
self
}
pub fn strikethrough(&mut self, strikethrough: bool) -> &mut Self {
self.add_attr(Attribute::Strikethrough(strikethrough));
self
}
pub fn font_descriptor(&mut self, font: impl Into<KeyOrValue<FontDescriptor>>) -> &mut Self {
self.add_attr(Attribute::font_descriptor(font));
self
}
pub fn link(&mut self, command: impl Into<Command>) -> &mut Self {
self.rich_text_builder
.links
.push(Link::new(self.range.clone(), command.into()));
self
}
}