embedded_text/plugin/
mod.rs

1//! Plugins allow changing TextBox behaviour.
2//!
3//! Note: Custom plugins are experimental. If you wish to implement custom plugins,
4//! you need to activate the `plugin` feature.
5
6use core::{
7    cell::UnsafeCell,
8    hash::{Hash, Hasher},
9    marker::PhantomData,
10    ptr::addr_of,
11};
12use embedded_graphics::{
13    draw_target::DrawTarget,
14    prelude::PixelColor,
15    primitives::Rectangle,
16    text::renderer::{CharacterStyle, TextRenderer},
17};
18
19use crate::{
20    parser::{Parser, Token},
21    rendering::{cursor::Cursor, TextBoxProperties},
22};
23
24#[cfg(feature = "plugin")]
25pub mod private;
26#[cfg(feature = "plugin")]
27pub use private::Plugin;
28
29#[cfg(not(feature = "plugin"))]
30mod private;
31#[cfg(not(feature = "plugin"))]
32use private::Plugin;
33
34#[cfg(feature = "ansi")]
35pub mod ansi;
36pub mod tail;
37
38#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
39pub(crate) enum ProcessingState {
40    Measure,
41    Render,
42}
43
44/// Placeholder type when no plugin is used.
45#[derive(Clone, Copy, Default)]
46pub struct NoPlugin<C>(PhantomData<C>)
47where
48    C: PixelColor;
49
50impl<C> NoPlugin<C>
51where
52    C: PixelColor,
53{
54    pub(crate) const fn new() -> Self {
55        Self(PhantomData)
56    }
57}
58
59/// Plugin marker trait.
60///
61/// This trait is an implementation detail. Most likely you don't need to implement this.
62#[cfg_attr(
63    feature = "plugin",
64    doc = "If you wish to implement a plugin, see [Plugin]."
65)]
66// TODO: remove this trait once Plugin is stabilized, then move Plugin here
67pub trait PluginMarker<'a, C: PixelColor>: Plugin<'a, C> {}
68
69impl<'a, C, T> PluginMarker<'a, C> for T
70where
71    T: Plugin<'a, C>,
72    C: PixelColor,
73{
74}
75
76#[derive(Clone, Debug)]
77pub(crate) struct PluginInner<'a, M, C> {
78    plugin: M,
79    state: ProcessingState,
80    peeked_token: Option<Token<'a, C>>,
81}
82
83#[derive(Debug)]
84pub(crate) struct PluginWrapper<'a, M, C> {
85    inner: UnsafeCell<PluginInner<'a, M, C>>,
86}
87
88impl<'a, M: Clone, C: Clone> Clone for PluginWrapper<'a, M, C> {
89    fn clone(&self) -> Self {
90        Self {
91            inner: UnsafeCell::new(self.with(|this| PluginInner {
92                plugin: this.plugin.clone(),
93                state: this.state,
94                peeked_token: unsafe { addr_of!(this.peeked_token).read() },
95            })),
96        }
97    }
98}
99
100impl<'a, M, C> Hash for PluginWrapper<'a, M, C>
101where
102    C: PixelColor,
103{
104    fn hash<H: Hasher>(&self, state: &mut H) {
105        self.with(|this| this.state.hash(state))
106    }
107}
108
109impl<'a, M, C> PluginWrapper<'a, M, C> {
110    pub fn new(plugin: M) -> Self {
111        Self {
112            inner: UnsafeCell::new(PluginInner {
113                plugin,
114                state: ProcessingState::Measure,
115                peeked_token: None,
116            }),
117        }
118    }
119
120    pub fn into_inner(self) -> M {
121        self.inner.into_inner().plugin
122    }
123
124    fn with<R>(&self, cb: impl FnOnce(&PluginInner<'a, M, C>) -> R) -> R {
125        let inner = unsafe {
126            // SAFETY: This is safe because we aren't exposing the reference.
127            self.inner.get().as_ref().unwrap_unchecked()
128        };
129
130        cb(inner)
131    }
132
133    fn with_mut<R>(&self, cb: impl FnOnce(&mut PluginInner<'a, M, C>) -> R) -> R {
134        let inner = unsafe {
135            // SAFETY: This is safe because we aren't exposing the reference.
136            self.inner.get().as_mut().unwrap_unchecked()
137        };
138
139        cb(inner)
140    }
141}
142
143impl<'a, M, C> PluginWrapper<'a, M, C>
144where
145    C: PixelColor,
146    M: private::Plugin<'a, C>,
147{
148    pub fn new_line(&self) {
149        self.with_mut(|this| this.plugin.new_line());
150    }
151
152    pub fn set_state(&self, state: ProcessingState) {
153        self.with_mut(|this| this.state = state);
154    }
155
156    #[inline]
157    pub fn render_token(&self, token: Token<'a, C>) -> Option<Token<'a, C>> {
158        self.with_mut(|this| match this.state {
159            ProcessingState::Measure => Some(token),
160            ProcessingState::Render => this.plugin.render_token(token),
161        })
162    }
163
164    pub fn peek_token(&self, source: &mut Parser<'a, C>) -> Option<Token<'a, C>> {
165        self.with_mut(|this| {
166            if this.peeked_token.is_none() {
167                this.peeked_token = this.plugin.next_token(|| source.next());
168            }
169
170            this.peeked_token.clone()
171        })
172    }
173
174    pub fn consume_peeked_token(&self) {
175        self.with_mut(|this| this.peeked_token = None);
176    }
177
178    pub fn consume_partial(&self, len: usize) {
179        self.with_mut(|this| {
180            // Only string-like tokens can be partially consumed.
181            debug_assert!(matches!(
182                this.peeked_token,
183                Some(Token::Whitespace(_, _)) | Some(Token::Word(_))
184            ));
185
186            let skip_chars = |str: &'a str, n| {
187                let mut chars = str.chars();
188                for _ in 0..n {
189                    chars.next();
190                }
191                chars.as_str()
192            };
193
194            if let Some(token) = this.peeked_token.take() {
195                let token = match token {
196                    Token::Whitespace(count, seq) => {
197                        Token::Whitespace(count - len as u32, skip_chars(seq, len))
198                    }
199                    Token::Word(w) => Token::Word(skip_chars(w, len)),
200                    _ => return,
201                };
202
203                this.peeked_token.replace(token);
204            }
205        })
206    }
207
208    pub fn on_start_render<S: CharacterStyle + TextRenderer>(
209        &self,
210        cursor: &mut Cursor,
211        props: TextBoxProperties<'_, S>,
212    ) {
213        self.with_mut(|this| {
214            this.peeked_token = None;
215
216            this.plugin.on_start_render(cursor, &props);
217        });
218    }
219
220    pub fn on_rendering_finished(&self) {
221        self.with_mut(|this| this.plugin.on_rendering_finished());
222    }
223
224    pub fn post_render<T, D>(
225        &self,
226        draw_target: &mut D,
227        character_style: &T,
228        text: Option<&str>,
229        bounds: Rectangle,
230    ) -> Result<(), D::Error>
231    where
232        T: TextRenderer<Color = C>,
233        D: DrawTarget<Color = C>,
234    {
235        self.with_mut(|this| {
236            this.plugin
237                .post_render(draw_target, character_style, text, bounds)
238        })
239    }
240}