Skip to main content

gpui_base/input/base/
token_presentation.rs

1//! Presentation callbacks and measured geometry. None of this enters document history.
2use super::{InlineToken, InlineTokenSpan, InputBaseState, InputModeKind};
3use gpui::{AnyElement, App, Bounds, ClickEvent, Font, IntoElement, Pixels, Window};
4use std::{collections::HashMap, ops::Range, rc::Rc};
5
6/// Read-only context for a single inline renderer. Width is the full available row.
7#[derive(Clone)]
8pub struct InlineTokenContext {
9    span: InlineTokenSpan,
10    selected: bool,
11    disabled: bool,
12    readonly: bool,
13    line_height: Pixels,
14    available_width: Pixels,
15}
16impl InlineTokenContext {
17    pub fn token(&self) -> &InlineToken {
18        self.span.token()
19    }
20    pub fn range(&self) -> Range<usize> {
21        self.span.range()
22    }
23    pub fn is_selected(&self) -> bool {
24        self.selected
25    }
26    pub fn is_disabled(&self) -> bool {
27        self.disabled
28    }
29    pub fn is_readonly(&self) -> bool {
30        self.readonly
31    }
32    pub fn line_height(&self) -> Pixels {
33        self.line_height
34    }
35    pub fn available_width(&self) -> Pixels {
36        self.available_width
37    }
38}
39
40/// Current token snapshot delivered after releasing the editor's update borrow.
41#[derive(Clone)]
42pub struct InlineTokenClickEvent {
43    span: InlineTokenSpan,
44    bounds: Bounds<Pixels>,
45    event: ClickEvent,
46}
47impl InlineTokenClickEvent {
48    pub fn token(&self) -> &InlineToken {
49        self.span.token()
50    }
51    pub fn range(&self) -> Range<usize> {
52        self.span.range()
53    }
54    pub fn bounds(&self) -> Bounds<Pixels> {
55        self.bounds
56    }
57    /// The click that opened the token; keyboard activation reports a
58    /// keyboard click.
59    pub fn click(&self) -> &ClickEvent {
60        &self.event
61    }
62}
63
64/// A renderer installed by a styled control. Not part of the supported API.
65#[doc(hidden)]
66pub type InlineTokenRenderer = Rc<dyn Fn(&InlineTokenContext, &mut Window, &mut App) -> AnyElement>;
67/// A click listener installed by a styled control. Not part of the supported API.
68#[doc(hidden)]
69pub type InlineTokenClickListener = Rc<dyn Fn(&InlineTokenClickEvent, &mut Window, &mut App)>;
70
71/// Presentation shared by Base and styled controls. It owns no content.
72#[derive(Clone, Default)]
73pub(crate) struct InlineTokenPresentation {
74    renderer: Option<InlineTokenRenderer>,
75    listener: Option<InlineTokenClickListener>,
76    secret: bool,
77}
78impl InlineTokenPresentation {
79    pub(crate) fn token<R: IntoElement>(
80        mut self,
81        render: impl Fn(&InlineTokenContext, &mut Window, &mut App) -> R + 'static,
82    ) -> Self {
83        self.renderer = Some(Rc::new(move |token, window, cx| {
84            render(token, window, cx).into_any_element()
85        }));
86        self
87    }
88    pub(crate) fn on_token_click(
89        mut self,
90        listener: impl Fn(&InlineTokenClickEvent, &mut Window, &mut App) + 'static,
91    ) -> Self {
92        self.listener = Some(Rc::new(listener));
93        self
94    }
95    pub(super) fn has_listener(&self) -> bool {
96        self.listener.is_some()
97    }
98    pub(super) fn render(
99        &self,
100        token: &InlineTokenContext,
101        window: &mut Window,
102        cx: &mut App,
103    ) -> AnyElement {
104        if let Some(render) = &self.renderer {
105            render(token, window, cx)
106        } else {
107            gpui::div()
108                .child(token.token().label().clone())
109                .into_any_element()
110        }
111    }
112}
113use gpui::ParentElement as _;
114
115#[derive(Default)]
116pub(super) struct TokenLayoutCache {
117    pub(super) key: Option<(Font, Pixels, Pixels, Pixels, bool)>,
118    pub(super) revision: u64,
119    pub(super) unwrapped_width: Pixels,
120    pub(super) metrics: Rc<[(Range<usize>, Pixels)]>,
121    pub(super) widths: HashMap<InlineToken, Pixels>,
122}
123
124impl<M: InputModeKind> InputBaseState<M> {
125    /// Inject presentation from a view without editing or notifying the document.
126    pub(crate) fn set_token_presentation(&mut self, presentation: InlineTokenPresentation) {
127        self.token_presentation = presentation;
128    }
129    /// Install a styled control's renderer, click listener and secrecy without
130    /// editing or notifying the document. Not part of the supported API.
131    #[doc(hidden)]
132    pub fn install_token_presentation(
133        &mut self,
134        renderer: Option<InlineTokenRenderer>,
135        listener: Option<InlineTokenClickListener>,
136        secret: bool,
137    ) {
138        self.token_presentation = InlineTokenPresentation {
139            renderer,
140            listener,
141            secret,
142        };
143    }
144    pub(super) fn tokens_visible(&self) -> bool {
145        !self.masked
146            && !self.token_presentation.secret
147            && self.mask_pattern.is_none()
148            && !self.token_spans().is_empty()
149    }
150    pub(super) fn token_context(
151        &self,
152        span: &InlineTokenSpan,
153        line_height: Pixels,
154        width: Pixels,
155    ) -> InlineTokenContext {
156        let range = span.range();
157        let selection = self.selected_range();
158        InlineTokenContext {
159            span: span.clone(),
160            selected: selection.start < range.end && range.start < selection.end,
161            disabled: self.disabled,
162            readonly: self.readonly,
163            line_height,
164            available_width: width,
165        }
166    }
167    /// The token starting at `start`, paired with the listener that opens it.
168    pub(super) fn token_activation(
169        &self,
170        start: usize,
171        bounds: Bounds<Pixels>,
172        event: ClickEvent,
173    ) -> Option<(InlineTokenClickListener, InlineTokenClickEvent)> {
174        if self.disabled || !self.tokens_visible() {
175            return None;
176        }
177        let span = self
178            .token_spans()
179            .iter()
180            .find(|span| span.range().start == start)?
181            .clone();
182        Some((
183            self.token_presentation.listener.clone()?,
184            InlineTokenClickEvent {
185                span,
186                bounds,
187                event,
188            },
189        ))
190    }
191    pub(super) fn token_is_secret(&self) -> bool {
192        self.token_presentation.secret
193    }
194}