settings::read_vscode_theme reads a VS Code color theme, comments and trailing commas and all: its type picks the mode, its colors and token scopes name what they give, and the rest keep Ely's palette of that mode. settings::ThemeImporter takes one dropped or browsed for, shows its name, mode and count, and hands it over on Apply; a file that does not read says why. theme::syntax_themes offers Ely's code colors, Quiet and Paper, chosen through settings::SyntaxThemePicker, radio cards with a line of code in each palette. A card's words now shrink as a plain block, so a preview wraps inside the card at 280px.
116 lines
3.5 KiB
Rust
116 lines
3.5 KiB
Rust
use std::rc::Rc;
|
|
|
|
use gpui::{
|
|
App, ElementId, FontStyle, HighlightStyle, Hsla, InteractiveElement, IntoElement,
|
|
ParentElement, RenderOnce, SharedString, Styled, StyledText, Window, div,
|
|
};
|
|
|
|
use crate::{
|
|
forms::RadioCard,
|
|
theme::{ActiveTheme, Mode, Radius, Syntax, SyntaxTheme, TextSize},
|
|
typography::literal,
|
|
};
|
|
|
|
/// A line of code in `syntax`'s colors.
|
|
fn sample(syntax: &Syntax, cx: &App) -> impl IntoElement + use<> {
|
|
let theme = cx.theme();
|
|
let text = "fn answer() -> u32 { 42 } // truth";
|
|
let ink = |color: Hsla| HighlightStyle {
|
|
color: Some(color),
|
|
..Default::default()
|
|
};
|
|
let spans = [
|
|
(0..2, ink(syntax.keyword)),
|
|
(3..9, ink(syntax.function)),
|
|
(9..11, ink(syntax.punctuation)),
|
|
(12..14, ink(syntax.operator)),
|
|
(15..18, ink(syntax.type_name)),
|
|
(19..20, ink(syntax.punctuation)),
|
|
(21..23, ink(syntax.number)),
|
|
(24..25, ink(syntax.punctuation)),
|
|
(
|
|
26..34,
|
|
HighlightStyle {
|
|
color: Some(syntax.comment),
|
|
font_style: Some(FontStyle::Italic),
|
|
..Default::default()
|
|
},
|
|
),
|
|
];
|
|
literal(div())
|
|
.debug_selector(|| "syntax-sample".into())
|
|
.px_3()
|
|
.py_2()
|
|
.rounded(theme.radius(Radius::Md))
|
|
.bg(theme.colors.sunken)
|
|
.font_family(theme.mono_family.clone())
|
|
.text_size(theme.text_size(TextSize::Sm))
|
|
.text_color(theme.colors.fg)
|
|
.child(StyledText::new(text).with_highlights(spans))
|
|
}
|
|
|
|
type OnName = Rc<dyn Fn(&'static str, &mut Window, &mut App)>;
|
|
|
|
/// Code palettes to choose from, each a card with a line of code in its colors for the mode shown.
|
|
#[derive(IntoElement)]
|
|
pub struct SyntaxThemePicker {
|
|
id: ElementId,
|
|
themes: Vec<SyntaxTheme>,
|
|
selected: SharedString,
|
|
on_change: Option<OnName>,
|
|
}
|
|
|
|
impl SyntaxThemePicker {
|
|
/// `selected` names one of `themes`.
|
|
pub fn new(
|
|
id: impl Into<ElementId>,
|
|
themes: impl IntoIterator<Item = SyntaxTheme>,
|
|
selected: impl Into<SharedString>,
|
|
) -> Self {
|
|
let themes: Vec<SyntaxTheme> = themes.into_iter().collect();
|
|
let selected = selected.into();
|
|
assert!(
|
|
themes.iter().any(|theme| theme.name == selected.as_ref()),
|
|
"syntax theme picker: no theme {selected}"
|
|
);
|
|
Self {
|
|
id: id.into(),
|
|
themes,
|
|
selected,
|
|
on_change: None,
|
|
}
|
|
}
|
|
|
|
pub fn on_change(
|
|
mut self,
|
|
handler: impl Fn(&'static str, &mut Window, &mut App) + 'static,
|
|
) -> Self {
|
|
self.on_change = Some(Rc::new(handler));
|
|
self
|
|
}
|
|
}
|
|
|
|
impl RenderOnce for SyntaxThemePicker {
|
|
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
|
let id = self.id;
|
|
let on_change = self
|
|
.on_change
|
|
.unwrap_or_else(|| panic!("syntax theme picker {id:?} has no on_change"));
|
|
let mode: Mode = cx.theme().mode();
|
|
let cards = self.themes.iter().map(|theme| {
|
|
let (pick, name) = (on_change.clone(), theme.name);
|
|
RadioCard::new(
|
|
(id.clone(), format!("theme-{name}")),
|
|
self.selected.as_ref() == name,
|
|
name,
|
|
)
|
|
.preview(sample(&theme.of(mode), cx))
|
|
.on_select(move |window, cx| {
|
|
log::info!("syntax theme picker: {name}");
|
|
pick(name, window, cx)
|
|
})
|
|
});
|
|
div().flex().flex_col().gap_2().children(cards)
|
|
}
|
|
}
|