1use super::{PlotData, PlotError, plot_spec_from, with_plot_str};
4use crate::{ItemFlags, TextFlags, sys};
5
6pub struct TextPlot<'a> {
10 text: &'a str,
11 x: f64,
12 y: f64,
13 pix_offset_x: f64,
14 pix_offset_y: f64,
15 flags: TextFlags,
16 item_flags: ItemFlags,
17}
18
19impl<'a> TextPlot<'a> {
20 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 item_flags: ItemFlags::NONE,
30 }
31 }
32
33 pub fn with_pixel_offset(mut self, offset_x: f64, offset_y: f64) -> Self {
35 self.pix_offset_x = offset_x;
36 self.pix_offset_y = offset_y;
37 self
38 }
39
40 pub fn with_flags(mut self, flags: TextFlags) -> Self {
42 self.flags = flags;
43 self
44 }
45
46 pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
48 self.item_flags = flags;
49 self
50 }
51
52 pub fn vertical(mut self) -> Self {
54 self.flags |= TextFlags::VERTICAL;
55 self
56 }
57
58 pub fn validate(&self) -> Result<(), PlotError> {
60 if self.text.is_empty() {
61 return Err(PlotError::InvalidData("Text cannot be empty".to_string()));
62 }
63 if self.text.contains('\0') {
64 return Err(PlotError::StringConversion(
65 "text contained null byte".to_string(),
66 ));
67 }
68 Ok(())
69 }
70
71 pub fn plot(self) {
73 let pix_offset = sys::ImVec2_c {
74 x: self.pix_offset_x as f32,
75 y: self.pix_offset_y as f32,
76 };
77 let _ = with_plot_str(self.text, |text_ptr| unsafe {
78 let spec = plot_spec_from(
79 self.flags.bits() | self.item_flags.bits(),
80 0,
81 crate::IMPLOT_AUTO,
82 );
83 sys::ImPlot_PlotText(text_ptr, self.x, self.y, pix_offset, spec);
84 });
85 }
86}
87
88impl<'a> PlotData for TextPlot<'a> {
89 fn label(&self) -> &str {
90 self.text
91 }
92
93 fn data_len(&self) -> usize {
94 1 }
96}
97
98pub struct MultiTextPlot<'a> {
100 texts: Vec<&'a str>,
101 positions: Vec<(f64, f64)>,
102 pixel_offsets: Vec<(f64, f64)>,
103 flags: TextFlags,
104 item_flags: ItemFlags,
105}
106
107impl<'a> MultiTextPlot<'a> {
108 pub fn new(texts: Vec<&'a str>, positions: Vec<(f64, f64)>) -> Self {
110 let pixel_offsets = vec![(0.0, 0.0); texts.len()];
111 Self {
112 texts,
113 positions,
114 pixel_offsets,
115 flags: TextFlags::NONE,
116 item_flags: ItemFlags::NONE,
117 }
118 }
119
120 pub fn with_pixel_offsets(mut self, offsets: Vec<(f64, f64)>) -> Self {
122 self.pixel_offsets = offsets;
123 self
124 }
125
126 pub fn with_flags(mut self, flags: TextFlags) -> Self {
128 self.flags = flags;
129 self
130 }
131
132 pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
134 self.item_flags = flags;
135 self
136 }
137
138 pub fn vertical(mut self) -> Self {
140 self.flags |= TextFlags::VERTICAL;
141 self
142 }
143
144 pub fn validate(&self) -> Result<(), PlotError> {
146 if self.texts.len() != self.positions.len() {
147 return Err(PlotError::InvalidData(format!(
148 "Text count ({}) must match position count ({})",
149 self.texts.len(),
150 self.positions.len()
151 )));
152 }
153
154 if self.pixel_offsets.len() != self.texts.len() {
155 return Err(PlotError::InvalidData(format!(
156 "Pixel offset count ({}) must match text count ({})",
157 self.pixel_offsets.len(),
158 self.texts.len()
159 )));
160 }
161
162 if self.texts.is_empty() {
163 return Err(PlotError::EmptyData);
164 }
165
166 for (i, text) in self.texts.iter().enumerate() {
167 if text.is_empty() {
168 return Err(PlotError::InvalidData(format!(
169 "Text at index {} cannot be empty",
170 i
171 )));
172 }
173 }
174
175 Ok(())
176 }
177
178 pub fn plot(self) {
180 for (i, &text) in self.texts.iter().enumerate() {
181 let position = self.positions[i];
182 let offset = self.pixel_offsets[i];
183
184 let text_plot = TextPlot::new(text, position.0, position.1)
185 .with_pixel_offset(offset.0, offset.1)
186 .with_flags(self.flags)
187 .with_item_flags(self.item_flags);
188
189 text_plot.plot();
190 }
191 }
192}
193
194impl<'a> PlotData for MultiTextPlot<'a> {
195 fn label(&self) -> &str {
196 "MultiText"
197 }
198
199 fn data_len(&self) -> usize {
200 self.texts.len()
201 }
202}
203
204pub struct FormattedTextPlot {
206 text: String,
207 x: f64,
208 y: f64,
209 pix_offset_x: f64,
210 pix_offset_y: f64,
211 flags: TextFlags,
212 item_flags: ItemFlags,
213}
214
215impl FormattedTextPlot {
216 pub fn new(text: String, x: f64, y: f64) -> Self {
218 Self {
219 text,
220 x,
221 y,
222 pix_offset_x: 0.0,
223 pix_offset_y: 0.0,
224 flags: TextFlags::NONE,
225 item_flags: ItemFlags::NONE,
226 }
227 }
228
229 pub fn from_format(x: f64, y: f64, args: std::fmt::Arguments) -> Self {
231 Self::new(format!("{}", args), x, y)
232 }
233
234 pub fn with_pixel_offset(mut self, offset_x: f64, offset_y: f64) -> Self {
236 self.pix_offset_x = offset_x;
237 self.pix_offset_y = offset_y;
238 self
239 }
240
241 pub fn with_flags(mut self, flags: TextFlags) -> Self {
243 self.flags = flags;
244 self
245 }
246
247 pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
249 self.item_flags = flags;
250 self
251 }
252
253 pub fn vertical(mut self) -> Self {
255 self.flags |= TextFlags::VERTICAL;
256 self
257 }
258
259 pub fn validate(&self) -> Result<(), PlotError> {
261 if self.text.is_empty() {
262 return Err(PlotError::InvalidData("Text cannot be empty".to_string()));
263 }
264 if self.text.contains('\0') {
265 return Err(PlotError::StringConversion(
266 "text contained null byte".to_string(),
267 ));
268 }
269 Ok(())
270 }
271
272 pub fn plot(self) {
274 let pix_offset = sys::ImVec2_c {
275 x: self.pix_offset_x as f32,
276 y: self.pix_offset_y as f32,
277 };
278 let _ = with_plot_str(&self.text, |text_ptr| unsafe {
279 let spec = plot_spec_from(
280 self.flags.bits() | self.item_flags.bits(),
281 0,
282 crate::IMPLOT_AUTO,
283 );
284 sys::ImPlot_PlotText(text_ptr, self.x, self.y, pix_offset, spec);
285 });
286 }
287}
288
289impl PlotData for FormattedTextPlot {
290 fn label(&self) -> &str {
291 &self.text
292 }
293
294 fn data_len(&self) -> usize {
295 1
296 }
297}
298
299#[macro_export]
301macro_rules! plot_text {
302 ($x:expr, $y:expr, $($arg:tt)*) => {
303 $crate::plots::text::FormattedTextPlot::from_format($x, $y, format_args!($($arg)*))
304 };
305}
306
307pub struct TextAnnotation<'a> {
309 text: &'a str,
310 x: f64,
311 y: f64,
312 auto_offset: bool,
313 flags: TextFlags,
314 item_flags: ItemFlags,
315}
316
317impl<'a> TextAnnotation<'a> {
318 pub fn new(text: &'a str, x: f64, y: f64) -> Self {
320 Self {
321 text,
322 x,
323 y,
324 auto_offset: true,
325 flags: TextFlags::NONE,
326 item_flags: ItemFlags::NONE,
327 }
328 }
329
330 pub fn no_auto_offset(mut self) -> Self {
332 self.auto_offset = false;
333 self
334 }
335
336 pub fn with_flags(mut self, flags: TextFlags) -> Self {
338 self.flags = flags;
339 self
340 }
341
342 pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
344 self.item_flags = flags;
345 self
346 }
347
348 pub fn vertical(mut self) -> Self {
350 self.flags |= TextFlags::VERTICAL;
351 self
352 }
353
354 pub fn plot(self) {
356 let offset = if self.auto_offset {
357 (5.0, -5.0)
359 } else {
360 (0.0, 0.0)
361 };
362
363 let text_plot = TextPlot::new(self.text, self.x, self.y)
364 .with_pixel_offset(offset.0, offset.1)
365 .with_flags(self.flags)
366 .with_item_flags(self.item_flags);
367
368 text_plot.plot();
369 }
370}