use std::cell::Cell;
use cranpose_ui::{
Brush, Color, TextStyle,
text::{RangeStyle, SpanStyle, TextDecoration, TextDrawStyle},
};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct TextPaint {
pub plain: bool,
pub color: Color,
pub baseline_shift: f32,
pub style_hash: u64,
}
impl TextPaint {
pub fn of(
style: &TextStyle,
style_hash: u64,
spans: &[RangeStyle<SpanStyle>],
font_size: f32,
) -> Self {
let span_style = &style.span_style;
let plain = span_style.background.is_none()
&& span_style.shadow.is_none()
&& span_style.brush.is_none()
&& !matches!(
span_style.draw_style,
Some(TextDrawStyle::Stroke { width }) if width.is_finite() && width > 0.0
)
&& !spans_override_foreground(spans)
&& !has_visible_decoration(spans, style);
Self {
plain,
color: resolve_text_color_without_gradient_fallback(style, Color::WHITE),
baseline_shift: span_style
.baseline_shift
.filter(|shift| shift.is_specified())
.map_or(0.0, |shift| -(shift.0 * font_size)),
style_hash,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct TextPaintCache(Cell<Option<TextPaint>>);
impl TextPaintCache {
pub fn holding(paint: TextPaint) -> Self {
Self(Cell::new(Some(paint)))
}
pub fn get_or(&self, compute: impl FnOnce() -> TextPaint) -> TextPaint {
if let Some(paint) = self.0.get() {
return paint;
}
let paint = compute();
self.0.set(Some(paint));
paint
}
}
impl PartialEq for TextPaintCache {
fn eq(&self, _other: &Self) -> bool {
true
}
}
pub fn resolve_text_color_without_gradient_fallback(
text_style: &TextStyle,
default: Color,
) -> Color {
let mut color = text_style
.span_style
.color
.or(match text_style.span_style.brush.as_ref() {
Some(Brush::Solid(color)) => Some(*color),
_ => None,
})
.unwrap_or(default);
if let Some(alpha) = text_style.span_style.alpha {
color.3 *= alpha.clamp(0.0, 1.0);
}
color
}
fn span_has_foreground_override(span_style: &SpanStyle) -> bool {
matches!(
span_style.brush.as_ref(),
Some(
Brush::LinearGradient { .. }
| Brush::RadialGradient { .. }
| Brush::SweepGradient { .. }
)
) || span_style.alpha.is_some()
|| span_style.draw_style.is_some()
}
pub fn spans_override_foreground(spans: &[RangeStyle<SpanStyle>]) -> bool {
spans
.iter()
.any(|span| span_has_foreground_override(&span.item))
}
pub fn spans_override_foreground_color(spans: &[RangeStyle<SpanStyle>]) -> bool {
spans
.iter()
.any(|span| span.item.color.is_some() || matches!(span.item.brush, Some(Brush::Solid(_))))
}
pub fn has_visible_decoration(spans: &[RangeStyle<SpanStyle>], global_style: &TextStyle) -> bool {
let visible = |decoration: Option<TextDecoration>| {
decoration.is_some_and(|decoration| decoration != TextDecoration::NONE)
};
visible(global_style.span_style.text_decoration)
|| spans.iter().any(|span| visible(span.item.text_decoration))
}