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, 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 }
108}
109
110pub 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 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 pub fn with_pixel_offsets(mut self, offsets: Vec<(f64, f64)>) -> Self {
142 self.pixel_offsets = offsets;
143 self
144 }
145
146 pub fn with_flags(mut self, flags: TextFlags) -> Self {
148 self.flags = flags;
149 self
150 }
151
152 pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
154 self.item_flags = flags;
155 self
156 }
157
158 pub fn vertical(mut self) -> Self {
160 self.flags |= TextFlags::VERTICAL;
161 self
162 }
163
164 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 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
225pub 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 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 pub fn from_format(x: f64, y: f64, args: std::fmt::Arguments) -> Self {
260 Self::new(format!("{}", args), x, y)
261 }
262
263 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 pub fn with_flags(mut self, flags: TextFlags) -> Self {
272 self.flags = flags;
273 self
274 }
275
276 pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
278 self.item_flags = flags;
279 self
280 }
281
282 pub fn vertical(mut self) -> Self {
284 self.flags |= TextFlags::VERTICAL;
285 self
286 }
287
288 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 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#[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
337pub 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 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 pub fn no_auto_offset(mut self) -> Self {
370 self.auto_offset = false;
371 self
372 }
373
374 pub fn with_flags(mut self, flags: TextFlags) -> Self {
376 self.flags = flags;
377 self
378 }
379
380 pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
382 self.item_flags = flags;
383 self
384 }
385
386 pub fn vertical(mut self) -> Self {
388 self.flags |= TextFlags::VERTICAL;
389 self
390 }
391
392 pub fn plot(self, plot_ui: &crate::PlotUi<'_>) {
394 let offset = if self.auto_offset {
395 (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}