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