1use super::{
4 PlotData, PlotDataLayout, PlotError, PlotItemStyle, PlotItemStyled, plot_spec_with_style,
5 with_plot_str,
6};
7use crate::{ItemFlags, TextFlags, sys};
8
9pub 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 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 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 pub fn with_flags(mut self, flags: TextFlags) -> Self {
53 self.flags = flags;
54 self
55 }
56
57 pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
59 self.item_flags = flags;
60 self
61 }
62
63 pub fn vertical(mut self) -> Self {
65 self.flags |= TextFlags::VERTICAL;
66 self
67 }
68
69 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 pub fn plot(self) {
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 _ = with_plot_str(self.text, |text_ptr| unsafe {
89 let spec = plot_spec_with_style(
90 self.style,
91 self.flags.bits() | self.item_flags.bits(),
92 PlotDataLayout::DEFAULT,
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 PlotDataLayout::DEFAULT,
311 );
312 sys::ImPlot_PlotText(text_ptr, self.x, self.y, pix_offset, spec);
313 });
314 }
315}
316
317impl PlotData for FormattedTextPlot {
318 fn label(&self) -> &str {
319 &self.text
320 }
321
322 fn data_len(&self) -> usize {
323 1
324 }
325}
326
327#[macro_export]
329macro_rules! plot_text {
330 ($x:expr, $y:expr, $($arg:tt)*) => {
331 $crate::plots::text::FormattedTextPlot::from_format($x, $y, format_args!($($arg)*))
332 };
333}
334
335pub struct TextAnnotation<'a> {
337 text: &'a str,
338 x: f64,
339 y: f64,
340 style: PlotItemStyle,
341 auto_offset: bool,
342 flags: TextFlags,
343 item_flags: ItemFlags,
344}
345
346impl<'a> super::PlotItemStyled for TextAnnotation<'a> {
347 fn style_mut(&mut self) -> &mut PlotItemStyle {
348 &mut self.style
349 }
350}
351
352impl<'a> TextAnnotation<'a> {
353 pub fn new(text: &'a str, x: f64, y: f64) -> Self {
355 Self {
356 text,
357 x,
358 y,
359 style: PlotItemStyle::default(),
360 auto_offset: true,
361 flags: TextFlags::NONE,
362 item_flags: ItemFlags::NONE,
363 }
364 }
365
366 pub fn no_auto_offset(mut self) -> Self {
368 self.auto_offset = false;
369 self
370 }
371
372 pub fn with_flags(mut self, flags: TextFlags) -> Self {
374 self.flags = flags;
375 self
376 }
377
378 pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
380 self.item_flags = flags;
381 self
382 }
383
384 pub fn vertical(mut self) -> Self {
386 self.flags |= TextFlags::VERTICAL;
387 self
388 }
389
390 pub fn plot(self) {
392 let offset = if self.auto_offset {
393 (5.0, -5.0)
395 } else {
396 (0.0, 0.0)
397 };
398
399 let text_plot = TextPlot::new(self.text, self.x, self.y)
400 .with_style(self.style)
401 .with_pixel_offset(offset.0, offset.1)
402 .with_flags(self.flags)
403 .with_item_flags(self.item_flags);
404
405 text_plot.plot();
406 }
407}