1use super::{PlotData, PlotError, safe_cstring};
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 Ok(())
57 }
58
59 pub fn plot(self) {
61 let text_cstring = safe_cstring(self.text);
62
63 let pix_offset = sys::ImVec2_c {
64 x: self.pix_offset_x as f32,
65 y: self.pix_offset_y as f32,
66 };
67
68 unsafe {
69 sys::ImPlot_PlotText(
70 text_cstring.as_ptr(),
71 self.x,
72 self.y,
73 pix_offset,
74 self.flags.bits() as i32,
75 );
76 }
77 }
78}
79
80impl<'a> PlotData for TextPlot<'a> {
81 fn label(&self) -> &str {
82 self.text
83 }
84
85 fn data_len(&self) -> usize {
86 1 }
88}
89
90pub struct MultiTextPlot<'a> {
92 texts: Vec<&'a str>,
93 positions: Vec<(f64, f64)>,
94 pixel_offsets: Vec<(f64, f64)>,
95 flags: TextFlags,
96}
97
98impl<'a> MultiTextPlot<'a> {
99 pub fn new(texts: Vec<&'a str>, positions: Vec<(f64, f64)>) -> Self {
101 let pixel_offsets = vec![(0.0, 0.0); texts.len()];
102 Self {
103 texts,
104 positions,
105 pixel_offsets,
106 flags: TextFlags::NONE,
107 }
108 }
109
110 pub fn with_pixel_offsets(mut self, offsets: Vec<(f64, f64)>) -> Self {
112 self.pixel_offsets = offsets;
113 self
114 }
115
116 pub fn with_flags(mut self, flags: TextFlags) -> Self {
118 self.flags = flags;
119 self
120 }
121
122 pub fn vertical(mut self) -> Self {
124 self.flags |= TextFlags::VERTICAL;
125 self
126 }
127
128 pub fn validate(&self) -> Result<(), PlotError> {
130 if self.texts.len() != self.positions.len() {
131 return Err(PlotError::InvalidData(format!(
132 "Text count ({}) must match position count ({})",
133 self.texts.len(),
134 self.positions.len()
135 )));
136 }
137
138 if self.pixel_offsets.len() != self.texts.len() {
139 return Err(PlotError::InvalidData(format!(
140 "Pixel offset count ({}) must match text count ({})",
141 self.pixel_offsets.len(),
142 self.texts.len()
143 )));
144 }
145
146 if self.texts.is_empty() {
147 return Err(PlotError::EmptyData);
148 }
149
150 for (i, text) in self.texts.iter().enumerate() {
151 if text.is_empty() {
152 return Err(PlotError::InvalidData(format!(
153 "Text at index {} cannot be empty",
154 i
155 )));
156 }
157 }
158
159 Ok(())
160 }
161
162 pub fn plot(self) {
164 for (i, &text) in self.texts.iter().enumerate() {
165 let position = self.positions[i];
166 let offset = self.pixel_offsets[i];
167
168 let text_plot = TextPlot::new(text, position.0, position.1)
169 .with_pixel_offset(offset.0, offset.1)
170 .with_flags(self.flags);
171
172 text_plot.plot();
173 }
174 }
175}
176
177impl<'a> PlotData for MultiTextPlot<'a> {
178 fn label(&self) -> &str {
179 "MultiText"
180 }
181
182 fn data_len(&self) -> usize {
183 self.texts.len()
184 }
185}
186
187pub struct FormattedTextPlot {
189 text: String,
190 x: f64,
191 y: f64,
192 pix_offset_x: f64,
193 pix_offset_y: f64,
194 flags: TextFlags,
195}
196
197impl FormattedTextPlot {
198 pub fn new(text: String, x: f64, y: f64) -> Self {
200 Self {
201 text,
202 x,
203 y,
204 pix_offset_x: 0.0,
205 pix_offset_y: 0.0,
206 flags: TextFlags::NONE,
207 }
208 }
209
210 pub fn from_format(x: f64, y: f64, args: std::fmt::Arguments) -> Self {
212 Self::new(format!("{}", args), x, y)
213 }
214
215 pub fn with_pixel_offset(mut self, offset_x: f64, offset_y: f64) -> Self {
217 self.pix_offset_x = offset_x;
218 self.pix_offset_y = offset_y;
219 self
220 }
221
222 pub fn with_flags(mut self, flags: TextFlags) -> Self {
224 self.flags = flags;
225 self
226 }
227
228 pub fn vertical(mut self) -> Self {
230 self.flags |= TextFlags::VERTICAL;
231 self
232 }
233
234 pub fn validate(&self) -> Result<(), PlotError> {
236 if self.text.is_empty() {
237 return Err(PlotError::InvalidData("Text cannot be empty".to_string()));
238 }
239 Ok(())
240 }
241
242 pub fn plot(self) {
244 let text_cstring = safe_cstring(&self.text);
245
246 let pix_offset = sys::ImVec2_c {
247 x: self.pix_offset_x as f32,
248 y: self.pix_offset_y as f32,
249 };
250
251 unsafe {
252 sys::ImPlot_PlotText(
253 text_cstring.as_ptr(),
254 self.x,
255 self.y,
256 pix_offset,
257 self.flags.bits() as i32,
258 );
259 }
260 }
261}
262
263impl PlotData for FormattedTextPlot {
264 fn label(&self) -> &str {
265 &self.text
266 }
267
268 fn data_len(&self) -> usize {
269 1
270 }
271}
272
273#[macro_export]
275macro_rules! plot_text {
276 ($x:expr, $y:expr, $($arg:tt)*) => {
277 $crate::plots::text::FormattedTextPlot::from_format($x, $y, format_args!($($arg)*))
278 };
279}
280
281pub struct TextAnnotation<'a> {
283 text: &'a str,
284 x: f64,
285 y: f64,
286 auto_offset: bool,
287 flags: TextFlags,
288}
289
290impl<'a> TextAnnotation<'a> {
291 pub fn new(text: &'a str, x: f64, y: f64) -> Self {
293 Self {
294 text,
295 x,
296 y,
297 auto_offset: true,
298 flags: TextFlags::NONE,
299 }
300 }
301
302 pub fn no_auto_offset(mut self) -> Self {
304 self.auto_offset = false;
305 self
306 }
307
308 pub fn with_flags(mut self, flags: TextFlags) -> Self {
310 self.flags = flags;
311 self
312 }
313
314 pub fn vertical(mut self) -> Self {
316 self.flags |= TextFlags::VERTICAL;
317 self
318 }
319
320 pub fn plot(self) {
322 let offset = if self.auto_offset {
323 (5.0, -5.0)
325 } else {
326 (0.0, 0.0)
327 };
328
329 let text_plot = TextPlot::new(self.text, self.x, self.y)
330 .with_pixel_offset(offset.0, offset.1)
331 .with_flags(self.flags);
332
333 text_plot.plot();
334 }
335}