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