introduce typography system and motion module

This commit is contained in:
2026-10-09 19:54:08 +07:00
parent 41e991dd30
commit df411bd044
53 changed files with 1116 additions and 457 deletions
+16 -10
View File
@@ -14,6 +14,7 @@ mod platform_kind;
mod scrollbar_mode;
mod syntax;
mod theme;
mod typography;
pub mod appearance;
@@ -38,6 +39,9 @@ pub use platform_kind::PlatformKind;
pub use scrollbar_mode::*;
pub use syntax::{HighlightKind, SyntaxPalette};
pub use theme::*;
pub use typography::{
DEFAULT_BASE_TEXT_SIZE, Metrics, TextStyle, Typeset, base_text_size, set_base_text_size,
};
/// Which appearance the app is painting. Bezel names this enum `Appearance`;
/// coop's `ThemeMode` predates the port and carries the same two variants.
@@ -78,6 +82,7 @@ pub trait AppExt {
fn set_brand(&mut self, brand: Brand);
fn set_palette(&mut self, build: PaletteBuilder);
fn set_appearance_mode(&mut self, mode: AppearanceMode);
fn set_base_text_size(&mut self, points: f32);
}
impl AppExt for App {
@@ -94,6 +99,11 @@ impl AppExt for App {
fn set_appearance_mode(&mut self, mode: AppearanceMode) {
appearance::set_mode(mode, self);
}
fn set_base_text_size(&mut self, points: f32) {
typography::set_base_text_size(points);
self.refresh_windows();
}
}
/// Re-install the palette for the current appearance.
@@ -123,12 +133,12 @@ pub fn sync_base(cx: &mut App) {
ScrollbarMode::Always => gpui_base::ScrollbarMode::Always,
};
let colors = theme.colors;
let font_family = theme.font_family.clone();
let font_sans = theme.font_sans.clone();
let base = gpui_base::Theme::global_mut(cx);
base.appearance = appearance;
base.scrollbar = base.scrollbar.clone().with_mode(scrollbar_mode);
base.tokens.typography.sans = font_family;
base.tokens.typography.sans = font_sans;
let tokens = &mut base.tokens.colors;
tokens.background = colors.bg;
@@ -166,10 +176,8 @@ impl ActiveTheme for App {
pub struct Theme {
pub colors: ThemeColors,
pub mode: ThemeMode,
pub font_family: SharedString,
pub font_body: SharedString,
pub font_sans: SharedString,
pub font_mono: SharedString,
pub font_size: Pixels,
pub shadow: bool,
pub scrollbar_mode: ScrollbarMode,
pub notification: NotificationSettings,
@@ -272,16 +280,14 @@ impl Default for Theme {
let platform = PlatformKind::platform();
let mode = ThemeMode::default();
// Define the font family based on the platform.
let font_family = match platform {
// The system UI font, by platform.
let font_sans = match platform {
PlatformKind::Linux => "Inter",
_ => ".SystemUIFont",
};
Self {
font_size: px(15.),
font_family: font_family.into(),
font_body: font_family.into(),
font_sans: font_sans.into(),
font_mono: system_mono().into(),
shadow: true,
scrollbar_mode: ScrollbarMode::default(),
+131
View File
@@ -0,0 +1,131 @@
use std::sync::atomic::{AtomicU32, Ordering};
use gpui::{FontWeight, Styled, px};
/// The base the ladder starts at; bezel's measured body is 13, coop keeps 15.
pub const DEFAULT_BASE_TEXT_SIZE: f32 = 15.0;
/// The body size every painted role is scaled against, as raw `f32` bits.
static BASE: AtomicU32 = AtomicU32::new(DEFAULT_BASE_TEXT_SIZE.to_bits());
/// Point the ladder at a body size in points; the caller refreshes windows.
pub fn set_base_text_size(points: f32) {
BASE.store(points.max(1.0).to_bits(), Ordering::Relaxed);
}
/// The body size in points every painted role is scaled against.
pub fn base_text_size() -> f32 {
f32::from_bits(BASE.load(Ordering::Relaxed))
}
/// A role in the type ladder — SwiftUI's `Font.TextStyle`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TextStyle {
LargeTitle,
Title,
Title2,
Title3,
Headline,
Subheadline,
Body,
Callout,
Footnote,
Caption,
Caption2,
}
impl TextStyle {
/// The role's measured size in points.
pub const fn size(self) -> f32 {
match self {
Self::LargeTitle => 26.0,
Self::Title => 22.0,
Self::Title2 => 17.0,
Self::Title3 => 15.0,
Self::Headline | Self::Body => 13.0,
Self::Callout => 12.0,
Self::Subheadline => 11.0,
Self::Footnote | Self::Caption | Self::Caption2 => 10.0,
}
}
pub fn painted(self) -> f32 {
self.size() * base_text_size() / Self::Body.size()
}
pub const fn line_height(self) -> f32 {
match self {
Self::LargeTitle => 32.0,
Self::Title => 26.0,
Self::Title2 => 22.0,
Self::Title3 => 20.0,
Self::Headline | Self::Body => 16.0,
Self::Callout => 15.0,
Self::Subheadline => 14.0,
Self::Footnote | Self::Caption | Self::Caption2 => 13.0,
}
}
pub fn painted_line_height(self) -> f32 {
self.line_height() * base_text_size() / Self::Body.size()
}
pub const fn weight(self) -> FontWeight {
match self {
Self::Headline => FontWeight::BOLD,
Self::Caption2 => FontWeight::MEDIUM,
_ => FontWeight::NORMAL,
}
}
}
/// One role as it is set: a rung on the ladder, its leading, weight and scale.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Metrics {
pub role: TextStyle,
pub leading: f32,
pub weight: FontWeight,
pub scale: f32,
}
impl Metrics {
pub const fn new(role: TextStyle, leading: f32, weight: FontWeight) -> Self {
Self {
role,
leading,
weight,
scale: 1.0,
}
}
pub const fn scaled(self, scale: f32) -> Self {
Self { scale, ..self }
}
pub fn size(self) -> f32 {
self.role.painted() * self.scale
}
pub fn line_height(self) -> f32 {
self.size() * self.leading
}
}
impl From<TextStyle> for Metrics {
/// The ladder's own setting for a role: its measured leading and weight.
fn from(role: TextStyle) -> Self {
Self::new(role, role.line_height() / role.size(), role.weight())
}
}
/// The ladder, on anything styled.
pub trait Typeset: Styled + Sized {
/// Size, line height and weight together, from [`TextStyle`].
fn text_style(self, style: TextStyle) -> Self {
self.text_size(px(style.painted()))
.line_height(px(style.painted_line_height()))
.font_weight(style.weight())
}
}
impl<E: Styled> Typeset for E {}