use crate::theme::use_theme;
use kael::{prelude::FluentBuilder as _, *};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum TextVariant {
H1,
H2,
H3,
H4,
H5,
H6,
BodyLarge,
Body,
BodySmall,
Caption,
Label,
LabelSmall,
Code,
CodeSmall,
Custom,
}
impl TextVariant {
pub fn size(&self) -> Pixels {
match self {
Self::H1 => px(32.0),
Self::H2 => px(28.0),
Self::H3 => px(24.0),
Self::H4 => px(20.0),
Self::H5 => px(18.0),
Self::H6 => px(16.0),
Self::BodyLarge => px(16.0),
Self::Body => px(14.0),
Self::BodySmall => px(13.0),
Self::Caption => px(12.0),
Self::Label => px(14.0),
Self::LabelSmall => px(12.0),
Self::Code => px(14.0),
Self::CodeSmall => px(12.0),
Self::Custom => px(14.0), }
}
pub fn weight(&self) -> FontWeight {
match self {
Self::H1 => FontWeight::BOLD,
Self::H2 | Self::H3 | Self::H4 => FontWeight::SEMIBOLD,
Self::H5 | Self::H6 | Self::Label | Self::LabelSmall => FontWeight::MEDIUM,
Self::BodyLarge | Self::Body | Self::BodySmall | Self::Caption => FontWeight::NORMAL,
Self::Code | Self::CodeSmall => FontWeight::NORMAL,
Self::Custom => FontWeight::NORMAL,
}
}
pub fn is_mono(&self) -> bool {
matches!(self, Self::Code | Self::CodeSmall)
}
pub fn line_height(&self) -> f32 {
match self {
Self::H1 | Self::H2 | Self::H3 | Self::H4 => 1.2,
Self::H5 | Self::H6 => 1.3,
Self::BodyLarge | Self::Body | Self::BodySmall => 1.5,
Self::Caption | Self::Label | Self::LabelSmall => 1.4,
Self::Code | Self::CodeSmall => 1.6,
Self::Custom => 1.5,
}
}
}
#[derive(IntoElement)]
pub struct Text {
content: SharedString,
variant: TextVariant,
size: Option<Pixels>,
weight: Option<FontWeight>,
color: Option<Hsla>,
font: Option<SharedString>,
line_height: Option<f32>,
italic: bool,
underline: bool,
strikethrough: bool,
wrap: bool,
truncate: bool,
style: StyleRefinement,
}
impl Text {
pub fn new<S: Into<SharedString>>(content: S) -> Self {
Self {
content: content.into(),
variant: TextVariant::Body,
size: None,
weight: None,
color: None,
font: None,
line_height: None,
italic: false,
underline: false,
strikethrough: false,
wrap: true,
truncate: false,
style: StyleRefinement::default(),
}
}
pub fn variant(mut self, variant: TextVariant) -> Self {
self.variant = variant;
self
}
pub fn size(mut self, size: Pixels) -> Self {
self.size = Some(size);
self
}
pub fn weight(mut self, weight: FontWeight) -> Self {
self.weight = Some(weight);
self
}
pub fn color(mut self, color: Hsla) -> Self {
self.color = Some(color);
self
}
pub fn font(mut self, font: impl Into<SharedString>) -> Self {
self.font = Some(font.into());
self
}
pub fn line_height(mut self, line_height: f32) -> Self {
self.line_height = Some(line_height);
self
}
pub fn italic(mut self) -> Self {
self.italic = true;
self
}
pub fn underline(mut self) -> Self {
self.underline = true;
self
}
pub fn strikethrough(mut self) -> Self {
self.strikethrough = true;
self
}
pub fn no_wrap(mut self) -> Self {
self.wrap = false;
self
}
pub fn truncate(mut self) -> Self {
self.truncate = true;
self.wrap = false; self
}
fn effective_size(&self) -> Pixels {
self.size.unwrap_or_else(|| self.variant.size())
}
fn effective_weight(&self) -> FontWeight {
self.weight.unwrap_or_else(|| self.variant.weight())
}
fn effective_line_height(&self) -> f32 {
self.line_height
.unwrap_or_else(|| self.variant.line_height())
}
}
impl Styled for Text {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.style
}
}
impl RenderOnce for Text {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
let theme = use_theme();
let size = self.effective_size();
let weight = self.effective_weight();
let line_height = self.effective_line_height();
let font_family = if let Some(font) = self.font {
font
} else if self.variant.is_mono() {
theme.tokens.font_mono.clone()
} else {
theme.tokens.font_family.clone()
};
let text_color = self.color.unwrap_or(theme.tokens.foreground);
let mut base = div();
*base.style() = self.style;
let needs_highlights = self.italic || self.strikethrough;
let styled_text = if needs_highlights {
let mut highlight_style = HighlightStyle::default();
if self.italic {
highlight_style.font_style = Some(FontStyle::Italic);
}
if self.strikethrough {
highlight_style.strikethrough = Some(StrikethroughStyle {
color: Some(text_color),
thickness: px(1.0),
});
}
let text_len = self.content.len();
StyledText::new(self.content.clone())
.with_highlights(vec![(0..text_len, highlight_style)])
} else {
StyledText::new(self.content.clone())
};
base.font_family(font_family)
.text_size(size)
.font_weight(weight)
.text_color(text_color)
.line_height(relative(line_height))
.when(self.underline, |this| this.underline())
.when(!self.wrap, |this| this.whitespace_nowrap())
.when(self.truncate, |this| this.overflow_hidden().text_ellipsis())
.child(styled_text)
}
}
pub fn h1<S: Into<SharedString>>(content: S) -> Text {
Text::new(content).variant(TextVariant::H1)
}
pub fn h2<S: Into<SharedString>>(content: S) -> Text {
Text::new(content).variant(TextVariant::H2)
}
pub fn h3<S: Into<SharedString>>(content: S) -> Text {
Text::new(content).variant(TextVariant::H3)
}
pub fn h4<S: Into<SharedString>>(content: S) -> Text {
Text::new(content).variant(TextVariant::H4)
}
pub fn h5<S: Into<SharedString>>(content: S) -> Text {
Text::new(content).variant(TextVariant::H5)
}
pub fn h6<S: Into<SharedString>>(content: S) -> Text {
Text::new(content).variant(TextVariant::H6)
}
pub fn body<S: Into<SharedString>>(content: S) -> Text {
Text::new(content).variant(TextVariant::Body)
}
pub fn body_large<S: Into<SharedString>>(content: S) -> Text {
Text::new(content).variant(TextVariant::BodyLarge)
}
pub fn body_small<S: Into<SharedString>>(content: S) -> Text {
Text::new(content).variant(TextVariant::BodySmall)
}
pub fn caption<S: Into<SharedString>>(content: S) -> Text {
Text::new(content).variant(TextVariant::Caption)
}
pub fn label<S: Into<SharedString>>(content: S) -> Text {
Text::new(content).variant(TextVariant::Label)
}
pub fn label_small<S: Into<SharedString>>(content: S) -> Text {
Text::new(content).variant(TextVariant::LabelSmall)
}
pub fn code<S: Into<SharedString>>(content: S) -> Text {
Text::new(content).variant(TextVariant::Code)
}
pub fn code_small<S: Into<SharedString>>(content: S) -> Text {
Text::new(content).variant(TextVariant::CodeSmall)
}
pub fn muted<S: Into<SharedString>>(content: S) -> Text {
let theme = use_theme();
Text::new(content)
.variant(TextVariant::Body)
.color(theme.tokens.muted_foreground)
}
pub fn muted_small<S: Into<SharedString>>(content: S) -> Text {
let theme = use_theme();
Text::new(content)
.variant(TextVariant::BodySmall)
.color(theme.tokens.muted_foreground)
}