Skip to main content

dear_implot/plots/
text.rs

1//! Text plot implementation
2
3use super::{
4    PlotData, PlotDataLayout, PlotError, PlotItemStyle, PlotItemStyled, plot_spec_with_style,
5    with_plot_str,
6};
7use crate::{ItemFlags, TextFlags, sys};
8
9/// Builder for text plots with extensive customization options
10///
11/// Text plots allow placing text labels at specific coordinates in the plot area.
12pub struct TextPlot<'a> {
13    text: &'a str,
14    x: f64,
15    y: f64,
16    style: PlotItemStyle,
17    pix_offset_x: f64,
18    pix_offset_y: f64,
19    flags: TextFlags,
20    item_flags: ItemFlags,
21}
22
23impl<'a> super::PlotItemStyled for TextPlot<'a> {
24    fn style_mut(&mut self) -> &mut PlotItemStyle {
25        &mut self.style
26    }
27}
28
29impl<'a> TextPlot<'a> {
30    /// Create a new text plot with the given text and position
31    pub fn new(text: &'a str, x: f64, y: f64) -> Self {
32        Self {
33            text,
34            x,
35            y,
36            style: PlotItemStyle::default(),
37            pix_offset_x: 0.0,
38            pix_offset_y: 0.0,
39            flags: TextFlags::NONE,
40            item_flags: ItemFlags::NONE,
41        }
42    }
43
44    /// Set pixel offset for fine positioning
45    pub fn with_pixel_offset(mut self, offset_x: f64, offset_y: f64) -> Self {
46        self.pix_offset_x = offset_x;
47        self.pix_offset_y = offset_y;
48        self
49    }
50
51    /// Set text flags for customization
52    pub fn with_flags(mut self, flags: TextFlags) -> Self {
53        self.flags = flags;
54        self
55    }
56
57    /// Set common item flags for this plot item (applies to all plot types)
58    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
59        self.item_flags = flags;
60        self
61    }
62
63    /// Make text vertical instead of horizontal
64    pub fn vertical(mut self) -> Self {
65        self.flags |= TextFlags::VERTICAL;
66        self
67    }
68
69    /// Validate the plot data
70    pub fn validate(&self) -> Result<(), PlotError> {
71        if self.text.is_empty() {
72            return Err(PlotError::InvalidData("Text cannot be empty".to_string()));
73        }
74        if self.text.contains('\0') {
75            return Err(PlotError::StringConversion(
76                "text contained null byte".to_string(),
77            ));
78        }
79        Ok(())
80    }
81
82    /// Plot the text
83    pub fn plot(self, plot_ui: &crate::PlotUi<'_>) {
84        let pix_offset = sys::ImVec2_c {
85            x: self.pix_offset_x as f32,
86            y: self.pix_offset_y as f32,
87        };
88        plot_ui.with_bound_context(|| {
89            let _ = with_plot_str(self.text, |text_ptr| unsafe {
90                let spec = plot_spec_with_style(
91                    self.style,
92                    self.flags.bits() | self.item_flags.bits(),
93                    PlotDataLayout::DEFAULT,
94                );
95                sys::ImPlot_PlotText(text_ptr, self.x, self.y, pix_offset, spec);
96            });
97        })
98    }
99}
100
101impl<'a> PlotData for TextPlot<'a> {
102    fn label(&self) -> &str {
103        self.text
104    }
105
106    fn data_len(&self) -> usize {
107        1 // Text plot has one data point
108    }
109}
110
111/// Multiple text labels plot
112pub struct MultiTextPlot<'a> {
113    texts: Vec<&'a str>,
114    positions: Vec<(f64, f64)>,
115    pixel_offsets: Vec<(f64, f64)>,
116    style: PlotItemStyle,
117    flags: TextFlags,
118    item_flags: ItemFlags,
119}
120
121impl<'a> super::PlotItemStyled for MultiTextPlot<'a> {
122    fn style_mut(&mut self) -> &mut PlotItemStyle {
123        &mut self.style
124    }
125}
126
127impl<'a> MultiTextPlot<'a> {
128    /// Create a new multi-text plot
129    pub fn new(texts: Vec<&'a str>, positions: Vec<(f64, f64)>) -> Self {
130        let pixel_offsets = vec![(0.0, 0.0); texts.len()];
131        Self {
132            texts,
133            positions,
134            pixel_offsets,
135            style: PlotItemStyle::default(),
136            flags: TextFlags::NONE,
137            item_flags: ItemFlags::NONE,
138        }
139    }
140
141    /// Set pixel offsets for all texts
142    pub fn with_pixel_offsets(mut self, offsets: Vec<(f64, f64)>) -> Self {
143        self.pixel_offsets = offsets;
144        self
145    }
146
147    /// Set text flags for all texts
148    pub fn with_flags(mut self, flags: TextFlags) -> Self {
149        self.flags = flags;
150        self
151    }
152
153    /// Set common item flags for all text items (applies to all plot types)
154    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
155        self.item_flags = flags;
156        self
157    }
158
159    /// Make all texts vertical
160    pub fn vertical(mut self) -> Self {
161        self.flags |= TextFlags::VERTICAL;
162        self
163    }
164
165    /// Validate the plot data
166    pub fn validate(&self) -> Result<(), PlotError> {
167        if self.texts.len() != self.positions.len() {
168            return Err(PlotError::InvalidData(format!(
169                "Text count ({}) must match position count ({})",
170                self.texts.len(),
171                self.positions.len()
172            )));
173        }
174
175        if self.pixel_offsets.len() != self.texts.len() {
176            return Err(PlotError::InvalidData(format!(
177                "Pixel offset count ({}) must match text count ({})",
178                self.pixel_offsets.len(),
179                self.texts.len()
180            )));
181        }
182
183        if self.texts.is_empty() {
184            return Err(PlotError::EmptyData);
185        }
186
187        for (i, text) in self.texts.iter().enumerate() {
188            if text.is_empty() {
189                return Err(PlotError::InvalidData(format!(
190                    "Text at index {} cannot be empty",
191                    i
192                )));
193            }
194        }
195
196        Ok(())
197    }
198
199    /// Plot all texts
200    pub fn plot(self, plot_ui: &crate::PlotUi<'_>) {
201        for (i, &text) in self.texts.iter().enumerate() {
202            let position = self.positions[i];
203            let offset = self.pixel_offsets[i];
204
205            let text_plot = TextPlot::new(text, position.0, position.1)
206                .with_style(self.style)
207                .with_pixel_offset(offset.0, offset.1)
208                .with_flags(self.flags)
209                .with_item_flags(self.item_flags);
210
211            text_plot.plot(plot_ui);
212        }
213    }
214}
215
216impl<'a> PlotData for MultiTextPlot<'a> {
217    fn label(&self) -> &str {
218        "MultiText"
219    }
220
221    fn data_len(&self) -> usize {
222        self.texts.len()
223    }
224}
225
226/// Formatted text plot with dynamic content
227pub struct FormattedTextPlot {
228    text: String,
229    x: f64,
230    y: f64,
231    style: PlotItemStyle,
232    pix_offset_x: f64,
233    pix_offset_y: f64,
234    flags: TextFlags,
235    item_flags: ItemFlags,
236}
237
238impl super::PlotItemStyled for FormattedTextPlot {
239    fn style_mut(&mut self) -> &mut PlotItemStyle {
240        &mut self.style
241    }
242}
243
244impl FormattedTextPlot {
245    /// Create a new formatted text plot
246    pub fn new(text: String, x: f64, y: f64) -> Self {
247        Self {
248            text,
249            x,
250            y,
251            style: PlotItemStyle::default(),
252            pix_offset_x: 0.0,
253            pix_offset_y: 0.0,
254            flags: TextFlags::NONE,
255            item_flags: ItemFlags::NONE,
256        }
257    }
258
259    /// Create a formatted text plot from format arguments
260    pub fn from_format(x: f64, y: f64, args: std::fmt::Arguments) -> Self {
261        Self::new(format!("{}", args), x, y)
262    }
263
264    /// Set pixel offset for fine positioning
265    pub fn with_pixel_offset(mut self, offset_x: f64, offset_y: f64) -> Self {
266        self.pix_offset_x = offset_x;
267        self.pix_offset_y = offset_y;
268        self
269    }
270
271    /// Set text flags for customization
272    pub fn with_flags(mut self, flags: TextFlags) -> Self {
273        self.flags = flags;
274        self
275    }
276
277    /// Set common item flags for this plot item (applies to all plot types)
278    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
279        self.item_flags = flags;
280        self
281    }
282
283    /// Make text vertical
284    pub fn vertical(mut self) -> Self {
285        self.flags |= TextFlags::VERTICAL;
286        self
287    }
288
289    /// Validate the plot data
290    pub fn validate(&self) -> Result<(), PlotError> {
291        if self.text.is_empty() {
292            return Err(PlotError::InvalidData("Text cannot be empty".to_string()));
293        }
294        if self.text.contains('\0') {
295            return Err(PlotError::StringConversion(
296                "text contained null byte".to_string(),
297            ));
298        }
299        Ok(())
300    }
301
302    /// Plot the formatted text
303    pub fn plot(self, plot_ui: &crate::PlotUi<'_>) {
304        let pix_offset = sys::ImVec2_c {
305            x: self.pix_offset_x as f32,
306            y: self.pix_offset_y as f32,
307        };
308        plot_ui.with_bound_context(|| {
309            let _ = with_plot_str(&self.text, |text_ptr| unsafe {
310                let spec = plot_spec_with_style(
311                    self.style,
312                    self.flags.bits() | self.item_flags.bits(),
313                    PlotDataLayout::DEFAULT,
314                );
315                sys::ImPlot_PlotText(text_ptr, self.x, self.y, pix_offset, spec);
316            });
317        })
318    }
319}
320
321impl PlotData for FormattedTextPlot {
322    fn label(&self) -> &str {
323        &self.text
324    }
325
326    fn data_len(&self) -> usize {
327        1
328    }
329}
330
331/// Convenience macro for creating formatted text plots
332#[macro_export]
333macro_rules! plot_text {
334    ($x:expr, $y:expr, $($arg:tt)*) => {
335        $crate::plots::text::FormattedTextPlot::from_format($x, $y, format_args!($($arg)*))
336    };
337}
338
339/// Text annotation with automatic positioning
340pub struct TextAnnotation<'a> {
341    text: &'a str,
342    x: f64,
343    y: f64,
344    style: PlotItemStyle,
345    auto_offset: bool,
346    flags: TextFlags,
347    item_flags: ItemFlags,
348}
349
350impl<'a> super::PlotItemStyled for TextAnnotation<'a> {
351    fn style_mut(&mut self) -> &mut PlotItemStyle {
352        &mut self.style
353    }
354}
355
356impl<'a> TextAnnotation<'a> {
357    /// Create a new text annotation
358    pub fn new(text: &'a str, x: f64, y: f64) -> Self {
359        Self {
360            text,
361            x,
362            y,
363            style: PlotItemStyle::default(),
364            auto_offset: true,
365            flags: TextFlags::NONE,
366            item_flags: ItemFlags::NONE,
367        }
368    }
369
370    /// Disable automatic offset calculation
371    pub fn no_auto_offset(mut self) -> Self {
372        self.auto_offset = false;
373        self
374    }
375
376    /// Set text flags
377    pub fn with_flags(mut self, flags: TextFlags) -> Self {
378        self.flags = flags;
379        self
380    }
381
382    /// Set common item flags for the annotation text
383    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
384        self.item_flags = flags;
385        self
386    }
387
388    /// Make text vertical
389    pub fn vertical(mut self) -> Self {
390        self.flags |= TextFlags::VERTICAL;
391        self
392    }
393
394    /// Plot the annotation
395    pub fn plot(self, plot_ui: &crate::PlotUi<'_>) {
396        let offset = if self.auto_offset {
397            // Simple auto-offset logic - could be enhanced
398            (5.0, -5.0)
399        } else {
400            (0.0, 0.0)
401        };
402
403        let text_plot = TextPlot::new(self.text, self.x, self.y)
404            .with_style(self.style)
405            .with_pixel_offset(offset.0, offset.1)
406            .with_flags(self.flags)
407            .with_item_flags(self.item_flags);
408
409        text_plot.plot(plot_ui);
410    }
411}