use crate::theme::{ActiveTheme, Themeable};
use gpui::{
App, FontWeight, IntoElement, ParentElement, RenderOnce, SharedString, Styled, Window, div,
rems,
};
const TYPESCALE_RATIO: f32 = 1.2;
const BASE_SIZE: f32 = 1.0;
#[derive(Default, Clone, Copy, PartialEq, Eq)]
pub enum TextAlign {
#[default]
Left,
Center,
Right,
}
#[derive(Default, Clone, Copy, PartialEq, Eq)]
pub enum TextVariant {
#[default]
Default,
Muted,
Destructive,
Accent,
}
pub fn h1(content: impl Into<SharedString>) -> Heading {
Heading::new(content, 1)
}
pub fn h2(content: impl Into<SharedString>) -> Heading {
Heading::new(content, 2)
}
pub fn h3(content: impl Into<SharedString>) -> Heading {
Heading::new(content, 3)
}
pub fn h4(content: impl Into<SharedString>) -> Heading {
Heading::new(content, 4)
}
#[derive(IntoElement)]
pub struct Heading {
content: SharedString,
level: u8,
align: TextAlign,
truncate: bool,
variant: TextVariant,
}
impl Heading {
pub fn new(content: impl Into<SharedString>, level: u8) -> Self {
Self {
content: content.into(),
level: level.clamp(1, 4),
align: TextAlign::default(),
truncate: false,
variant: TextVariant::default(),
}
}
pub fn align(mut self, align: TextAlign) -> Self {
self.align = align;
self
}
pub fn truncate(mut self, truncate: bool) -> Self {
self.truncate = truncate;
self
}
pub fn variant(mut self, variant: TextVariant) -> Self {
self.variant = variant;
self
}
pub fn muted(mut self) -> Self {
self.variant = TextVariant::Muted;
self
}
pub fn destructive(mut self) -> Self {
self.variant = TextVariant::Destructive;
self
}
pub fn accent(mut self) -> Self {
self.variant = TextVariant::Accent;
self
}
fn font_size(&self) -> f32 {
let scale_power = match self.level {
1 => 4,
2 => 3,
3 => 2,
4 => 1,
_ => 0,
};
BASE_SIZE * TYPESCALE_RATIO.powi(scale_power)
}
}
impl RenderOnce for Heading {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let theme = cx.theme();
let size = self.font_size();
let text_color = match self.variant {
TextVariant::Default => theme.fg(),
TextVariant::Muted => theme.fg_muted(),
TextVariant::Destructive => theme.danger(),
TextVariant::Accent => theme.accent(),
};
let mut el = div()
.w_full()
.text_size(rems(size))
.line_height(rems(size * 1.2))
.font_weight(FontWeight::BOLD)
.text_color(text_color);
el = match self.align {
TextAlign::Left => el,
TextAlign::Center => el.text_center(),
TextAlign::Right => el.text_right(),
};
if self.truncate {
el = el.truncate();
}
el.child(self.content)
}
}
pub fn p(content: impl Into<SharedString>) -> Paragraph {
Paragraph::new(content)
}
#[derive(IntoElement)]
pub struct Paragraph {
content: SharedString,
align: TextAlign,
truncate: bool,
variant: TextVariant,
}
impl Paragraph {
pub fn new(content: impl Into<SharedString>) -> Self {
Self {
content: content.into(),
align: TextAlign::default(),
truncate: false,
variant: TextVariant::default(),
}
}
pub fn align(mut self, align: TextAlign) -> Self {
self.align = align;
self
}
pub fn truncate(mut self, truncate: bool) -> Self {
self.truncate = truncate;
self
}
pub fn variant(mut self, variant: TextVariant) -> Self {
self.variant = variant;
self
}
pub fn muted(mut self) -> Self {
self.variant = TextVariant::Muted;
self
}
pub fn destructive(mut self) -> Self {
self.variant = TextVariant::Destructive;
self
}
pub fn accent(mut self) -> Self {
self.variant = TextVariant::Accent;
self
}
}
impl RenderOnce for Paragraph {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let theme = cx.theme();
let text_color = match self.variant {
TextVariant::Default => theme.fg(),
TextVariant::Muted => theme.fg_muted(),
TextVariant::Destructive => theme.danger(),
TextVariant::Accent => theme.accent(),
};
let mut el = div()
.w_full()
.text_size(rems(BASE_SIZE))
.line_height(rems(BASE_SIZE * 1.5))
.text_color(text_color);
el = match self.align {
TextAlign::Left => el,
TextAlign::Center => el.text_center(),
TextAlign::Right => el.text_right(),
};
if self.truncate {
el = el.truncate();
}
el.child(self.content)
}
}
pub fn text(content: impl Into<SharedString>) -> Text {
Text::new(content)
}
#[derive(IntoElement)]
pub struct Text {
content: SharedString,
align: TextAlign,
truncate: bool,
variant: TextVariant,
code: bool,
small: bool,
bold: bool,
}
impl Text {
pub fn new(content: impl Into<SharedString>) -> Self {
Self {
content: content.into(),
align: TextAlign::default(),
truncate: false,
variant: TextVariant::default(),
code: false,
small: false,
bold: false,
}
}
pub fn align(mut self, align: TextAlign) -> Self {
self.align = align;
self
}
pub fn truncate(mut self, truncate: bool) -> Self {
self.truncate = truncate;
self
}
pub fn variant(mut self, variant: TextVariant) -> Self {
self.variant = variant;
self
}
pub fn muted(mut self) -> Self {
self.variant = TextVariant::Muted;
self
}
pub fn destructive(mut self) -> Self {
self.variant = TextVariant::Destructive;
self
}
pub fn accent(mut self) -> Self {
self.variant = TextVariant::Accent;
self
}
pub fn code(mut self) -> Self {
self.code = true;
self
}
pub fn small(mut self) -> Self {
self.small = true;
self
}
pub fn bold(mut self) -> Self {
self.bold = true;
self
}
}
impl RenderOnce for Text {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let theme = cx.theme();
let text_color = match self.variant {
TextVariant::Default => theme.fg(),
TextVariant::Muted => theme.fg_muted(),
TextVariant::Destructive => theme.danger(),
TextVariant::Accent => theme.accent(),
};
let size = if self.small || self.code {
BASE_SIZE * 0.875
} else {
BASE_SIZE
};
let mut el = div()
.text_size(rems(size))
.line_height(rems(size * 1.5))
.text_color(text_color);
if self.bold {
el = el.font_weight(FontWeight::BOLD);
}
if self.code {
el = el.font_family("monospace");
}
el = match self.align {
TextAlign::Left => el,
TextAlign::Center => el.text_center(),
TextAlign::Right => el.text_right(),
};
if self.truncate {
el = el.truncate();
}
el.child(self.content)
}
}
pub fn lead(content: impl Into<SharedString>) -> Lead {
Lead::new(content)
}
#[derive(IntoElement)]
pub struct Lead {
content: SharedString,
align: TextAlign,
truncate: bool,
variant: TextVariant,
}
impl Lead {
pub fn new(content: impl Into<SharedString>) -> Self {
Self {
content: content.into(),
align: TextAlign::default(),
truncate: false,
variant: TextVariant::default(),
}
}
pub fn align(mut self, align: TextAlign) -> Self {
self.align = align;
self
}
pub fn truncate(mut self, truncate: bool) -> Self {
self.truncate = truncate;
self
}
pub fn variant(mut self, variant: TextVariant) -> Self {
self.variant = variant;
self
}
pub fn muted(mut self) -> Self {
self.variant = TextVariant::Muted;
self
}
}
impl RenderOnce for Lead {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let theme = cx.theme();
let size = BASE_SIZE * TYPESCALE_RATIO;
let text_color = match self.variant {
TextVariant::Default => theme.fg(),
TextVariant::Muted => theme.fg_muted(),
TextVariant::Destructive => theme.danger(),
TextVariant::Accent => theme.accent(),
};
let mut el = div()
.w_full()
.text_size(rems(size))
.line_height(rems(size * 1.5))
.text_color(text_color);
el = match self.align {
TextAlign::Left => el,
TextAlign::Center => el.text_center(),
TextAlign::Right => el.text_right(),
};
if self.truncate {
el = el.truncate();
}
el.child(self.content)
}
}
pub fn small(content: impl Into<SharedString>) -> Small {
Small::new(content)
}
#[derive(IntoElement)]
pub struct Small {
content: SharedString,
align: TextAlign,
truncate: bool,
variant: TextVariant,
}
impl Small {
pub fn new(content: impl Into<SharedString>) -> Self {
Self {
content: content.into(),
align: TextAlign::default(),
truncate: false,
variant: TextVariant::Muted, }
}
pub fn align(mut self, align: TextAlign) -> Self {
self.align = align;
self
}
pub fn truncate(mut self, truncate: bool) -> Self {
self.truncate = truncate;
self
}
pub fn variant(mut self, variant: TextVariant) -> Self {
self.variant = variant;
self
}
pub fn default_color(mut self) -> Self {
self.variant = TextVariant::Default;
self
}
pub fn muted(mut self) -> Self {
self.variant = TextVariant::Muted;
self
}
}
impl RenderOnce for Small {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let theme = cx.theme();
let size = BASE_SIZE * 0.875;
let text_color = match self.variant {
TextVariant::Default => theme.fg(),
TextVariant::Muted => theme.fg_muted(),
TextVariant::Destructive => theme.danger(),
TextVariant::Accent => theme.accent(),
};
let mut el = div()
.text_size(rems(size))
.line_height(rems(size * 1.5))
.text_color(text_color);
el = match self.align {
TextAlign::Left => el,
TextAlign::Center => el.text_center(),
TextAlign::Right => el.text_right(),
};
if self.truncate {
el = el.truncate();
}
el.child(self.content)
}
}
pub fn blockquote(content: impl Into<SharedString>) -> Blockquote {
Blockquote::new(content)
}
#[derive(IntoElement)]
pub struct Blockquote {
content: SharedString,
align: TextAlign,
}
impl Blockquote {
pub fn new(content: impl Into<SharedString>) -> Self {
Self {
content: content.into(),
align: TextAlign::default(),
}
}
pub fn align(mut self, align: TextAlign) -> Self {
self.align = align;
self
}
}
impl RenderOnce for Blockquote {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let theme = cx.theme();
let mut el = div()
.w_full()
.pl(rems(1.0))
.border_l_2()
.border_color(theme.border())
.text_size(rems(BASE_SIZE))
.line_height(rems(BASE_SIZE * 1.5))
.text_color(theme.fg_muted())
.italic();
el = match self.align {
TextAlign::Left => el,
TextAlign::Center => el.text_center(),
TextAlign::Right => el.text_right(),
};
el.child(self.content)
}
}