Skip to main content

markdown/paint/
marks.rs

1//! How the registered marks and a reader's highlights paint, and the
2//! registry the editing surface reads.
3
4use gpui::{App, FontWeight, Global, Hsla};
5use theme::Theme;
6
7use crate::Marks;
8
9impl Marks {
10    /// What the editing surface reads a document with, or nothing registered.
11    pub(crate) fn of(cx: &App) -> Self {
12        cx.try_global::<Installed>()
13            .map_or_else(Self::default, |installed| installed.0.clone())
14    }
15}
16
17struct Installed(Marks);
18
19impl Global for Installed {}
20
21/// `cx.set_marks(my_marks)` — call once at boot, so the editing
22/// surface reads and writes the same markdown the app's own calls do.
23pub(crate) fn set_marks(cx: &mut App, marks: Marks) {
24    cx.set_global(Installed(marks));
25}
26
27/// How a custom mark paints: nothing a layout would have to move for.
28#[derive(Clone, Copy, Debug, Default, PartialEq)]
29pub struct MarkPaint {
30    pub color: Option<Hsla>,
31    /// Painted under the text and over the selection.
32    pub background: Option<Hsla>,
33    pub weight: Option<FontWeight>,
34    pub italic: bool,
35    pub underline: bool,
36    pub strikethrough: bool,
37}
38
39/// What a registered mark looks like. `None` for a name this build does not
40/// paint, which reads as ordinary text.
41pub type Painter = fn(name: &str, theme: &Theme) -> Option<MarkPaint>;
42
43struct InstalledPaint(Painter);
44
45impl Global for InstalledPaint {}
46
47/// `cx.set_mark_paint(my_paint)` — call once at boot. Without it a
48/// custom mark round trips and paints as the text it wraps, which is what an
49/// unknown mark should look like rather than a hole.
50pub(crate) fn set_mark_paint(cx: &mut App, paint: Painter) {
51    cx.set_global(InstalledPaint(paint));
52}
53
54pub(crate) fn paint_of(cx: &App, name: &str, theme: &Theme) -> Option<MarkPaint> {
55    (cx.try_global::<InstalledPaint>()?.0)(name, theme)
56}
57
58/// A reader's highlight colour, by name — the set Apple Books and Notes offer.
59///
60/// A name rather than a colour value, so a highlight saved under one look
61/// paints under another. [`crate::AppExt::set_highlight_paint`] decides what each one paints.
62#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
63#[non_exhaustive]
64pub enum HighlightColor {
65    #[default]
66    Yellow,
67    Green,
68    Blue,
69    Pink,
70    Purple,
71}
72
73impl HighlightColor {
74    /// Every colour, in the order a picker shows them.
75    pub const ALL: [Self; 5] = [
76        Self::Yellow,
77        Self::Green,
78        Self::Blue,
79        Self::Pink,
80        Self::Purple,
81    ];
82
83    /// The name to store. Stable across releases; [`Self::from_name`] reads it
84    /// back.
85    pub fn name(self) -> &'static str {
86        match self {
87            Self::Yellow => "yellow",
88            Self::Green => "green",
89            Self::Blue => "blue",
90            Self::Pink => "pink",
91            Self::Purple => "purple",
92        }
93    }
94
95    pub fn from_name(name: &str) -> Option<Self> {
96        Self::ALL.into_iter().find(|color| color.name() == name)
97    }
98
99    /// The system colour it paints in.
100    pub fn system(self) -> ui::color::SystemColor {
101        match self {
102            Self::Yellow => ui::color::SystemColor::Yellow,
103            Self::Green => ui::color::SystemColor::Green,
104            Self::Blue => ui::color::SystemColor::Blue,
105            Self::Pink => ui::color::SystemColor::Pink,
106            Self::Purple => ui::color::SystemColor::Purple,
107        }
108    }
109}
110
111/// The wash a [`HighlightColor`] paints under text.
112pub type HighlightPaint = fn(color: HighlightColor, theme: &Theme) -> Hsla;
113
114struct InstalledHighlight(HighlightPaint);
115
116impl Global for InstalledHighlight {}
117
118/// `cx.set_highlight_paint(my_paint)` — call once at boot. Without
119/// it each colour paints [`default_highlight`].
120pub(crate) fn set_highlight_paint(cx: &mut App, paint: HighlightPaint) {
121    cx.set_global(InstalledHighlight(paint));
122}
123
124pub(crate) fn highlight_paint_of(cx: &App) -> HighlightPaint {
125    cx.try_global::<InstalledHighlight>()
126        .map_or(default_highlight, |installed| installed.0)
127}
128
129/// A [`HighlightColor`] itself, at full strength: its
130/// [`ui::color::SystemColor`] for the appearance. What a picker paints a
131/// swatch in.
132pub fn highlight_solid(color: HighlightColor, theme: &Theme) -> Hsla {
133    color.system().resolve(theme)
134}
135
136/// The wash a highlight of `color` paints under text: `color` at 32% in the
137/// dark appearance and 45% in the light.
138pub fn highlight_wash(color: Hsla, theme: &Theme) -> Hsla {
139    let alpha = match theme.appearance {
140        theme::Appearance::Dark => 0.32,
141        theme::Appearance::Light => 0.45,
142    };
143    color.opacity(alpha)
144}
145
146/// The shipped washes: [`highlight_wash`] of [`highlight_solid`].
147pub fn default_highlight(color: HighlightColor, theme: &Theme) -> Hsla {
148    highlight_wash(highlight_solid(color, theme), theme)
149}