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