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 plot_ui.with_bound_context(|| {
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}
100
101impl<'a> PlotData for TextPlot<'a> {
102 fn label(&self) -> &str {
103 self.text
104 }
105
106 fn data_len(&self) -> usize {
107 1 }
109}
110
111pub struct MultiTextPlot<'a> {
113 texts: Vec<&'a str>,
114 positions: Vec<(f64, f64)>,
115 pixel_offsets: Vec<(f64, f64)>,
116 style: PlotItemStyle,
117 flags: TextFlags,
118 item_flags: ItemFlags,
119}
120
121impl<'a> super::PlotItemStyled for MultiTextPlot<'a> {
122 fn style_mut(&mut self) -> &mut PlotItemStyle {
123 &mut self.style
124 }
125}
126
127impl<'a> MultiTextPlot<'a> {
128 pub fn new(texts: Vec<&'a str>, positions: Vec<(f64, f64)>) -> Self {
130 let pixel_offsets = vec![(0.0, 0.0); texts.len()];
131 Self {
132 texts,
133 positions,
134 pixel_offsets,
135 style: PlotItemStyle::default(),
136 flags: TextFlags::NONE,
137 item_flags: ItemFlags::NONE,
138 }
139 }
140
141 pub fn with_pixel_offsets(mut self, offsets: Vec<(f64, f64)>) -> Self {
143 self.pixel_offsets = offsets;
144 self
145 }
146
147 pub fn with_flags(mut self, flags: TextFlags) -> Self {
149 self.flags = flags;
150 self
151 }
152
153 pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
155 self.item_flags = flags;
156 self
157 }
158
159 pub fn vertical(mut self) -> Self {
161 self.flags |= TextFlags::VERTICAL;
162 self
163 }
164
165 pub fn validate(&self) -> Result<(), PlotError> {
167 if self.texts.len() != self.positions.len() {
168 return Err(PlotError::InvalidData(format!(
169 "Text count ({}) must match position count ({})",
170 self.texts.len(),
171 self.positions.len()
172 )));
173 }
174
175 if self.pixel_offsets.len() != self.texts.len() {
176 return Err(PlotError::InvalidData(format!(
177 "Pixel offset count ({}) must match text count ({})",
178 self.pixel_offsets.len(),
179 self.texts.len()
180 )));
181 }
182
183 if self.texts.is_empty() {
184 return Err(PlotError::EmptyData);
185 }
186
187 for (i, text) in self.texts.iter().enumerate() {
188 if text.is_empty() {
189 return Err(PlotError::InvalidData(format!(
190 "Text at index {} cannot be empty",
191 i
192 )));
193 }
194 }
195
196 Ok(())
197 }
198
199 pub fn plot(self, plot_ui: &crate::PlotUi<'_>) {
201 for (i, &text) in self.texts.iter().enumerate() {
202 let position = self.positions[i];
203 let offset = self.pixel_offsets[i];
204
205 let text_plot = TextPlot::new(text, position.0, position.1)
206 .with_style(self.style)
207 .with_pixel_offset(offset.0, offset.1)
208 .with_flags(self.flags)
209 .with_item_flags(self.item_flags);
210
211 text_plot.plot(plot_ui);
212 }
213 }
214}
215
216impl<'a> PlotData for MultiTextPlot<'a> {
217 fn label(&self) -> &str {
218 "MultiText"
219 }
220
221 fn data_len(&self) -> usize {
222 self.texts.len()
223 }
224}
225
226pub struct FormattedTextPlot {
228 text: String,
229 x: f64,
230 y: f64,
231 style: PlotItemStyle,
232 pix_offset_x: f64,
233 pix_offset_y: f64,
234 flags: TextFlags,
235 item_flags: ItemFlags,
236}
237
238impl super::PlotItemStyled for FormattedTextPlot {
239 fn style_mut(&mut self) -> &mut PlotItemStyle {
240 &mut self.style
241 }
242}
243
244impl FormattedTextPlot {
245 pub fn new(text: String, x: f64, y: f64) -> Self {
247 Self {
248 text,
249 x,
250 y,
251 style: PlotItemStyle::default(),
252 pix_offset_x: 0.0,
253 pix_offset_y: 0.0,
254 flags: TextFlags::NONE,
255 item_flags: ItemFlags::NONE,
256 }
257 }
258
259 pub fn from_format(x: f64, y: f64, args: std::fmt::Arguments) -> Self {
261 Self::new(format!("{}", args), x, y)
262 }
263
264 pub fn with_pixel_offset(mut self, offset_x: f64, offset_y: f64) -> Self {
266 self.pix_offset_x = offset_x;
267 self.pix_offset_y = offset_y;
268 self
269 }
270
271 pub fn with_flags(mut self, flags: TextFlags) -> Self {
273 self.flags = flags;
274 self
275 }
276
277 pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
279 self.item_flags = flags;
280 self
281 }
282
283 pub fn vertical(mut self) -> Self {
285 self.flags |= TextFlags::VERTICAL;
286 self
287 }
288
289 pub fn validate(&self) -> Result<(), PlotError> {
291 if self.text.is_empty() {
292 return Err(PlotError::InvalidData("Text cannot be empty".to_string()));
293 }
294 if self.text.contains('\0') {
295 return Err(PlotError::StringConversion(
296 "text contained null byte".to_string(),
297 ));
298 }
299 Ok(())
300 }
301
302 pub fn plot(self, plot_ui: &crate::PlotUi<'_>) {
304 let pix_offset = sys::ImVec2_c {
305 x: self.pix_offset_x as f32,
306 y: self.pix_offset_y as f32,
307 };
308 plot_ui.with_bound_context(|| {
309 let _ = with_plot_str(&self.text, |text_ptr| unsafe {
310 let spec = plot_spec_with_style(
311 self.style,
312 self.flags.bits() | self.item_flags.bits(),
313 PlotDataLayout::DEFAULT,
314 );
315 sys::ImPlot_PlotText(text_ptr, self.x, self.y, pix_offset, spec);
316 });
317 })
318 }
319}
320
321impl PlotData for FormattedTextPlot {
322 fn label(&self) -> &str {
323 &self.text
324 }
325
326 fn data_len(&self) -> usize {
327 1
328 }
329}
330
331#[macro_export]
333macro_rules! plot_text {
334 ($x:expr, $y:expr, $($arg:tt)*) => {
335 $crate::plots::text::FormattedTextPlot::from_format($x, $y, format_args!($($arg)*))
336 };
337}
338
339pub struct TextAnnotation<'a> {
341 text: &'a str,
342 x: f64,
343 y: f64,
344 style: PlotItemStyle,
345 auto_offset: bool,
346 flags: TextFlags,
347 item_flags: ItemFlags,
348}
349
350impl<'a> super::PlotItemStyled for TextAnnotation<'a> {
351 fn style_mut(&mut self) -> &mut PlotItemStyle {
352 &mut self.style
353 }
354}
355
356impl<'a> TextAnnotation<'a> {
357 pub fn new(text: &'a str, x: f64, y: f64) -> Self {
359 Self {
360 text,
361 x,
362 y,
363 style: PlotItemStyle::default(),
364 auto_offset: true,
365 flags: TextFlags::NONE,
366 item_flags: ItemFlags::NONE,
367 }
368 }
369
370 pub fn no_auto_offset(mut self) -> Self {
372 self.auto_offset = false;
373 self
374 }
375
376 pub fn with_flags(mut self, flags: TextFlags) -> Self {
378 self.flags = flags;
379 self
380 }
381
382 pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
384 self.item_flags = flags;
385 self
386 }
387
388 pub fn vertical(mut self) -> Self {
390 self.flags |= TextFlags::VERTICAL;
391 self
392 }
393
394 pub fn plot(self, plot_ui: &crate::PlotUi<'_>) {
396 let offset = if self.auto_offset {
397 (5.0, -5.0)
399 } else {
400 (0.0, 0.0)
401 };
402
403 let text_plot = TextPlot::new(self.text, self.x, self.y)
404 .with_style(self.style)
405 .with_pixel_offset(offset.0, offset.1)
406 .with_flags(self.flags)
407 .with_item_flags(self.item_flags);
408
409 text_plot.plot(plot_ui);
410 }
411}