dear_implot/plots/
text.rs

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