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