Skip to main content

editor/
text_size.rs

1//! How big a document is set, and the chords that nudge it.
2//!
3//! Two numbers, the way Xcode and Zed split them. The *base* is the app's, in
4//! points: a settings field, handed to [`Editor::with_text_size`], and never
5//! touched from in here. The *adjustment* is the reader's — what `cmd-+` moves,
6//! shared by every open document, and held in memory only. A reset clears the
7//! adjustment, and the base is what is left.
8//!
9//! Nothing here is persisted: storage is the app's, not a component library's.
10//! The base comes from whatever settings the app keeps, and an app that wants
11//! the adjustment to outlive the process stores [`crate::AppExt::editor_text_size_adjustment`]
12//! beside it — beside, not folded into the base, or a reset has nowhere left
13//! to return to.
14//!
15//! [`Editor::with_text_size`]: crate::Editor::with_text_size
16
17use crate::AppExt as _;
18use gpui::{App, Global};
19
20use theme::TextStyle;
21
22/// How far the chords move a document, in points — the unit a settings field
23/// is written in, so the two never need converting between.
24#[derive(Clone, Copy, Debug, PartialEq)]
25pub struct TextSize {
26    /// Points per press. Whole points by default, so every stop is a size that
27    /// could be written into a settings file.
28    pub step: f32,
29    pub min: f32,
30    pub max: f32,
31}
32
33impl TextSize {
34    /// How the editor sizes, or [`TextSize::default`] before anything is
35    /// installed. Mirrors [`theme::Theme::of`].
36    pub(crate) fn of(cx: &App) -> Self {
37        cx.try_global::<Installed>()
38            .map_or_else(Self::default, |installed| installed.0)
39    }
40
41    /// A size brought inside the range, and rounded to a tenth of a point so a
42    /// run of steps lands on the same numbers each time.
43    pub(crate) fn clamp(self, points: f32) -> f32 {
44        (points.clamp(self.min, self.max) * 10.0).round() / 10.0
45    }
46}
47
48impl Default for TextSize {
49    /// A point per press, over the range the ladder itself stays legible in —
50    /// its smallest measured role at the bottom, twice the body at the top.
51    fn default() -> Self {
52        Self {
53            step: 1.0,
54            min: TextStyle::Caption2.size(),
55            max: TextStyle::Body.size() * 2.0,
56        }
57    }
58}
59
60struct Installed(TextSize);
61
62impl Global for Installed {}
63
64/// `cx.set_editor_text_size(my_steps)` — call once at boot.
65pub(crate) fn set_text_size(cx: &mut App, text_size: TextSize) {
66    cx.set_global(Installed(text_size));
67}
68
69/// What every document is currently set away from its base by, in points.
70///
71/// Zero unless a reader has reached for the chords. A host showing the size in
72/// a status bar reads this; one that does not care never has to know it exists.
73pub(crate) fn text_size_adjustment(cx: &App) -> f32 {
74    cx.try_global::<Adjustment>().map_or(0.0, |held| held.0)
75}
76
77/// Move every open document by `points`, each staying inside the range against
78/// its own base.
79pub(crate) fn adjust_text_size(cx: &mut App, points: f32) {
80    set_adjustment(cx.editor_text_size_adjustment() + points, cx);
81}
82
83/// Give every document back to the size its app set it at.
84pub(crate) fn reset_text_size(cx: &mut App) {
85    set_adjustment(0.0, cx);
86}
87
88pub(crate) fn set_adjustment(points: f32, cx: &mut App) {
89    cx.set_global(Adjustment(points));
90    // The adjustment is shared, so this is what moves the documents in every
91    // other window with it.
92    cx.refresh_windows();
93}
94
95/// The size a document with this base is actually set at.
96pub(crate) fn resolve(base: Option<f32>, cx: &App) -> f32 {
97    let base = base.unwrap_or_else(theme::base_text_size);
98    cx.editor_text_size()
99        .clamp(base + cx.editor_text_size_adjustment())
100}
101
102struct Adjustment(f32);
103
104impl Global for Adjustment {}