dear_implot/plots/
text.rs

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