Skip to main content

egui/atomics/
atoms.rs

1use crate::{Atom, AtomKind, Image, WidgetText};
2use core::ops::{Deref, DerefMut};
3use std::borrow::Cow;
4
5/// A list of [`Atom`]s.
6///
7/// Many widgets take an `impl` [`IntoAtoms`] parameter,
8/// which allows you to easily create atoms from tuples of text, images, and other atoms:
9/// ```
10/// # use egui::{AtomExt, AtomKind, Atom, Image, Id, Vec2};
11/// # egui::__run_test_ui(|ui| {
12/// let image = egui::include_image!("../../../eframe/data/icon.png");
13/// ui.button((image, "Click me!"));
14/// # });
15#[derive(Clone, Debug, Default)]
16pub struct Atoms<'a>(Vec<Atom<'a>>);
17
18impl<'a> Atoms<'a> {
19    pub fn new(atoms: impl IntoAtoms<'a>) -> Self {
20        atoms.into_atoms()
21    }
22
23    /// Insert a new [`Atom`] at the end of the list (right side).
24    pub fn push_right(&mut self, atom: impl Into<Atom<'a>>) {
25        self.0.push(atom.into());
26    }
27
28    /// Extend the list of atoms by appending more atoms to the right side.
29    ///
30    /// If you have weird lifetime issues with this, use [`Self::push_right`] in a loop instead.
31    pub fn extend_right(&mut self, atoms: Self) {
32        self.0.extend(atoms.0);
33    }
34
35    /// Insert a new [`Atom`] at the beginning of the list (left side).
36    pub fn push_left(&mut self, atom: impl Into<Atom<'a>>) {
37        self.0.insert(0, atom.into());
38    }
39
40    /// Extend the list of atoms by prepending more atoms to the left side.
41    ///
42    /// If you have weird lifetime issues with this, use [`Self::push_left`] in a loop instead.
43    pub fn extend_left(&mut self, mut atoms: Self) {
44        core::mem::swap(&mut atoms.0, &mut self.0);
45        self.0.extend(atoms.0);
46    }
47
48    /// Concatenate and return the text contents.
49    // TODO(lucasmerlin): It might not always make sense to return the concatenated text, e.g.
50    // in a submenu button there is a right text '⏵' which is now passed to the screen reader.
51    pub fn text(&self) -> Option<Cow<'_, str>> {
52        let mut string: Option<Cow<'_, str>> = None;
53        for atom in &self.0 {
54            if let AtomKind::Text(text) = &atom.kind {
55                if let Some(string) = &mut string {
56                    let string = string.to_mut();
57                    string.push(' ');
58                    string.push_str(text.text());
59                } else {
60                    string = Some(Cow::Borrowed(text.text()));
61                }
62            }
63        }
64
65        // If there is no text, try to find an image with alt text.
66        if string.is_none() {
67            string = self.iter().find_map(|a| match &a.kind {
68                AtomKind::Image(image) => image.alt_text.as_deref().map(Cow::Borrowed),
69                _ => None,
70            });
71        }
72
73        string
74    }
75
76    /// Do any of the atoms have shrink set to `true`?
77    pub fn any_shrink(&self) -> bool {
78        self.iter().any(|a| a.shrink)
79    }
80
81    pub fn iter_kinds(&self) -> impl Iterator<Item = &AtomKind<'a>> {
82        self.0.iter().map(|atom| &atom.kind)
83    }
84
85    pub fn iter_kinds_mut(&mut self) -> impl Iterator<Item = &mut AtomKind<'a>> {
86        self.0.iter_mut().map(|atom| &mut atom.kind)
87    }
88
89    pub fn iter_images(&self) -> impl Iterator<Item = &Image<'a>> {
90        self.iter_kinds().filter_map(|kind| {
91            if let AtomKind::Image(image) = kind {
92                Some(image)
93            } else {
94                None
95            }
96        })
97    }
98
99    pub fn iter_images_mut(&mut self) -> impl Iterator<Item = &mut Image<'a>> {
100        self.iter_kinds_mut().filter_map(|kind| {
101            if let AtomKind::Image(image) = kind {
102                Some(image)
103            } else {
104                None
105            }
106        })
107    }
108
109    pub fn iter_texts(&self) -> impl Iterator<Item = &WidgetText> + use<'_, 'a> {
110        self.iter_kinds().filter_map(|kind| {
111            if let AtomKind::Text(text) = kind {
112                Some(text)
113            } else {
114                None
115            }
116        })
117    }
118
119    pub fn iter_texts_mut(&mut self) -> impl Iterator<Item = &mut WidgetText> + use<'a, '_> {
120        self.iter_kinds_mut().filter_map(|kind| {
121            if let AtomKind::Text(text) = kind {
122                Some(text)
123            } else {
124                None
125            }
126        })
127    }
128
129    pub fn map_atoms(&mut self, mut f: impl FnMut(Atom<'a>) -> Atom<'a>) {
130        self.iter_mut()
131            .for_each(|atom| *atom = f(core::mem::take(atom)));
132    }
133
134    pub fn map_kind<F>(&mut self, mut f: F)
135    where
136        F: FnMut(AtomKind<'a>) -> AtomKind<'a>,
137    {
138        for kind in self.iter_kinds_mut() {
139            *kind = f(core::mem::take(kind));
140        }
141    }
142
143    pub fn map_images<F>(&mut self, mut f: F)
144    where
145        F: FnMut(Image<'a>) -> Image<'a>,
146    {
147        self.map_kind(|kind| {
148            if let AtomKind::Image(image) = kind {
149                AtomKind::Image(f(image))
150            } else {
151                kind
152            }
153        });
154    }
155
156    pub fn map_texts<F>(&mut self, mut f: F)
157    where
158        F: FnMut(WidgetText) -> WidgetText,
159    {
160        self.map_kind(|kind| {
161            if let AtomKind::Text(text) = kind {
162                AtomKind::Text(f(text))
163            } else {
164                kind
165            }
166        });
167    }
168}
169
170impl<'a> IntoIterator for Atoms<'a> {
171    type Item = Atom<'a>;
172    type IntoIter = std::vec::IntoIter<Atom<'a>>;
173
174    fn into_iter(self) -> Self::IntoIter {
175        self.0.into_iter()
176    }
177}
178
179/// Helper trait to convert a tuple of atoms into [`Atoms`].
180///
181/// ```
182/// use egui::{Atoms, Image, IntoAtoms, RichText};
183/// let atoms: Atoms = (
184///     "Some text",
185///     RichText::new("Some RichText"),
186///     Image::new("some_image_url"),
187/// ).into_atoms();
188/// ```
189impl<'a, T> IntoAtoms<'a> for T
190where
191    T: Into<Atom<'a>>,
192{
193    fn collect(self, atoms: &mut Atoms<'a>) {
194        atoms.push_right(self);
195    }
196}
197
198/// Trait for turning a tuple of [`Atom`]s into [`Atoms`].
199///
200/// Many widgets take an `impl` [`IntoAtoms`] parameter,
201/// which allows you to easily create atoms from tuples of text, images, and other atoms:
202/// ```
203/// # use egui::{AtomExt, AtomKind, Atom, Image, Id, Vec2};
204/// # egui::__run_test_ui(|ui| {
205/// let image = egui::include_image!("../../../eframe/data/icon.png");
206/// ui.button((image, "Click me!"));
207/// # });
208/// ```
209pub trait IntoAtoms<'a> {
210    fn collect(self, atoms: &mut Atoms<'a>);
211
212    fn into_atoms(self) -> Atoms<'a>
213    where
214        Self: Sized,
215    {
216        let mut atoms = Atoms::default();
217        self.collect(&mut atoms);
218        atoms
219    }
220}
221
222impl<'a> IntoAtoms<'a> for Atoms<'a> {
223    fn collect(self, atoms: &mut Self) {
224        atoms.0.extend(self.0);
225    }
226}
227
228macro_rules! all_the_atoms {
229    ($($T:ident),*) => {
230        impl<'a, $($T),*> IntoAtoms<'a> for ($($T),*)
231        where
232            $($T: IntoAtoms<'a>),*
233        {
234            fn collect(self, _atoms: &mut Atoms<'a>) {
235                #[allow(clippy::allow_attributes, non_snake_case)]
236                let ($($T),*) = self;
237                $($T.collect(_atoms);)*
238            }
239        }
240    };
241}
242
243all_the_atoms!();
244all_the_atoms!(T0, T1);
245all_the_atoms!(T0, T1, T2);
246all_the_atoms!(T0, T1, T2, T3);
247all_the_atoms!(T0, T1, T2, T3, T4);
248all_the_atoms!(T0, T1, T2, T3, T4, T5);
249
250impl<'a> Deref for Atoms<'a> {
251    type Target = [Atom<'a>];
252
253    fn deref(&self) -> &Self::Target {
254        &self.0
255    }
256}
257
258impl DerefMut for Atoms<'_> {
259    fn deref_mut(&mut self) -> &mut Self::Target {
260        &mut self.0
261    }
262}
263
264impl<'a, T: Into<Atom<'a>>> From<Vec<T>> for Atoms<'a> {
265    fn from(vec: Vec<T>) -> Self {
266        Atoms(vec.into_iter().map(Into::into).collect())
267    }
268}
269
270impl<'a, T: Into<Atom<'a>> + Clone> From<&[T]> for Atoms<'a> {
271    fn from(slice: &[T]) -> Self {
272        Atoms(slice.iter().cloned().map(Into::into).collect())
273    }
274}
275
276impl<'a, Item: Into<Atom<'a>>> FromIterator<Item> for Atoms<'a> {
277    fn from_iter<T: IntoIterator<Item = Item>>(iter: T) -> Self {
278        Atoms(iter.into_iter().map(Into::into).collect())
279    }
280}
281
282#[cfg(test)]
283mod tests {
284    use crate::Atoms;
285
286    #[test]
287    fn collect_atoms() {
288        let _: Atoms<'_> = ["Hello", "World"].into_iter().collect();
289        let _ = Atoms::from(vec!["Hi"]);
290        let _ = Atoms::from(["Hi"].as_slice());
291    }
292}