1use std::ops::RangeInclusive;
2
3use egui::Color32;
4use egui::Id;
5use egui::Shape;
6use egui::Stroke;
7use egui::TextStyle;
8use egui::Ui;
9use egui::WidgetText;
10use egui::epaint::TextShape;
11use emath::Align2;
12
13use crate::axis::PlotTransform;
14use crate::bounds::PlotBounds;
15use crate::bounds::PlotPoint;
16use crate::items::PlotGeometry;
17use crate::items::PlotItem;
18use crate::items::PlotItemBase;
19
20impl Text {
21 pub fn new(name: impl Into<String>, position: PlotPoint, text: impl Into<WidgetText>) -> Self {
22 Self {
23 base: PlotItemBase::new(name.into()),
24 text: text.into(),
25 position,
26 color: Color32::TRANSPARENT,
27 anchor: Align2::CENTER_CENTER,
28 }
29 }
30
31 #[inline]
33 pub fn color(mut self, color: impl Into<Color32>) -> Self {
34 self.color = color.into();
35 self
36 }
37
38 #[inline]
40 pub fn anchor(mut self, anchor: Align2) -> Self {
41 self.anchor = anchor;
42 self
43 }
44
45 #[expect(clippy::needless_pass_by_value, reason = "to allow various string types")]
55 #[inline]
56 pub fn name(mut self, name: impl ToString) -> Self {
57 self.base_mut().name = name.to_string();
58 self
59 }
60
61 #[inline]
65 pub fn highlight(mut self, highlight: bool) -> Self {
66 self.base_mut().highlight = highlight;
67 self
68 }
69
70 #[inline]
72 pub fn allow_hover(mut self, hovering: bool) -> Self {
73 self.base_mut().allow_hover = hovering;
74 self
75 }
76
77 #[inline]
82 pub fn id(mut self, id: impl Into<Id>) -> Self {
83 self.base_mut().id = id.into();
84 self
85 }
86}
87
88impl PlotItem for Text {
89 fn shapes(&self, ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
90 let color = if self.color == Color32::TRANSPARENT {
91 ui.style().visuals.text_color()
92 } else {
93 self.color
94 };
95
96 let galley =
97 self.text
98 .clone()
99 .into_galley(ui, Some(egui::TextWrapMode::Extend), f32::INFINITY, TextStyle::Small);
100
101 let pos = transform.position_from_point(&self.position);
102 let rect = self.anchor.anchor_size(pos, galley.size());
103
104 shapes.push(TextShape::new(rect.min, galley, color).into());
105
106 if self.base.highlight {
107 shapes.push(Shape::rect_stroke(
108 rect.expand(1.0),
109 1.0,
110 Stroke::new(0.5, color),
111 egui::StrokeKind::Outside,
112 ));
113 }
114 }
115
116 fn initialize(&mut self, _x_range: RangeInclusive<f64>) {}
117
118 fn color(&self) -> Color32 {
119 self.color
120 }
121
122 fn geometry(&self) -> PlotGeometry<'_> {
123 PlotGeometry::None
124 }
125
126 fn bounds(&self) -> PlotBounds {
127 let mut bounds = PlotBounds::NOTHING;
128 bounds.extend_with(&self.position);
129 bounds
130 }
131
132 fn base(&self) -> &PlotItemBase {
133 &self.base
134 }
135
136 fn base_mut(&mut self) -> &mut PlotItemBase {
137 &mut self.base
138 }
139}
140
141#[derive(Clone)]
143pub struct Text {
144 base: PlotItemBase,
145 pub(crate) text: WidgetText,
146 pub(crate) position: PlotPoint,
147 pub(crate) color: Color32,
148 pub(crate) anchor: Align2,
149}