Skip to main content

egui/atomics/
atom.rs

1use crate::{AtomKind, FontSelection, Id, IntoSizedArgs, IntoSizedResult, SizedAtom, Ui};
2use emath::{Align2, NumExt as _, Vec2};
3use epaint::text::TextWrapMode;
4
5/// A low-level ui building block.
6///
7/// This can be a piece of text, an image, or even a custom widget.
8/// It can be decorated with various layout hints, such as `grow`, `shrink`, `align`, and more.
9///
10/// `Atom` implements [`From`] for [`String`], [`str`], [`crate::Image`] and much more for convenience.
11///
12/// Many widgets take an `impl` [`crate::IntoAtoms`] parameter,
13/// which allows you to easily create atoms from tuples of text, images, and other atoms:
14/// ```
15/// # use egui::{Vec2, AtomExt, AtomKind, Atom, Image, Id};
16/// # egui::__run_test_ui(|ui| {
17/// let image = egui::include_image!("../../../eframe/data/icon.png");
18/// ui.button((image, "Click me!"));
19/// # });
20/// ```
21///
22/// You can directly call the `atom_*` methods on anything that implements `Into<Atom>`.
23/// ```
24/// # use egui::{Image, emath::Vec2};
25/// use egui::AtomExt as _;
26/// let string_atom = "Hello".atom_grow(true);
27/// let image_atom = Image::new("some_image_url").atom_size(Vec2::splat(20.0));
28/// ```
29#[derive(Clone, Debug)]
30pub struct Atom<'a> {
31    /// See [`crate::AtomExt::atom_id`]
32    pub id: Option<Id>,
33
34    /// See [`crate::AtomExt::atom_size`]
35    pub size: Option<Vec2>,
36
37    /// See [`crate::AtomExt::atom_max_size`]
38    pub max_size: Vec2,
39
40    /// See [`crate::AtomExt::atom_grow`]
41    pub grow: bool,
42
43    /// See [`crate::AtomExt::atom_shrink`]
44    pub shrink: bool,
45
46    /// See [`crate::AtomExt::atom_align`]
47    pub align: Align2,
48
49    /// The atom type / content
50    pub kind: AtomKind<'a>,
51}
52
53impl Default for Atom<'_> {
54    fn default() -> Self {
55        Atom {
56            id: None,
57            size: None,
58            max_size: Vec2::INFINITY,
59            grow: false,
60            shrink: false,
61            align: Align2::CENTER_CENTER,
62            kind: AtomKind::Empty,
63        }
64    }
65}
66
67impl<'a> Atom<'a> {
68    /// Create an empty [`Atom`] marked as `grow`.
69    ///
70    /// This will expand in size, allowing all preceding atoms to be left-aligned,
71    /// and all following atoms to be right-aligned
72    pub fn grow() -> Self {
73        Atom {
74            grow: true,
75            ..Default::default()
76        }
77    }
78
79    /// Create an [`AtomKind::Empty`] with a specific size.
80    ///
81    /// Example:
82    /// ```
83    /// # use egui::{AtomExt, AtomKind, Atom, Button, Id, __run_test_ui};
84    /// # use emath::Vec2;
85    /// # __run_test_ui(|ui| {
86    /// let id = Id::new("my_button");
87    /// let response = Button::new(("Hi!", Atom::custom(id, Vec2::splat(18.0)))).atom_ui(ui);
88    ///
89    /// let rect = response.rect(id);
90    /// if let Some(rect) = rect {
91    ///     ui.place(rect, Button::new("⏵"));
92    /// }
93    /// # });
94    /// ```
95    pub fn custom(id: Id, size: impl Into<Vec2>) -> Self {
96        Atom {
97            size: Some(size.into()),
98            kind: AtomKind::Empty,
99            id: Some(id),
100            ..Default::default()
101        }
102    }
103
104    /// Turn this into a [`SizedAtom`].
105    pub fn into_sized(
106        self,
107        ui: &Ui,
108        mut available_size: Vec2,
109        mut wrap_mode: Option<TextWrapMode>,
110        fallback_font: FontSelection,
111    ) -> SizedAtom<'a> {
112        if !self.shrink && self.max_size.x.is_infinite() {
113            wrap_mode = Some(TextWrapMode::Extend);
114        }
115        available_size = available_size.at_most(self.max_size);
116        if let Some(size) = self.size {
117            available_size = available_size.at_most(size);
118        }
119        if self.max_size.x.is_finite() {
120            wrap_mode = Some(TextWrapMode::Truncate);
121        }
122
123        let id = self.id;
124
125        let wrap_mode = wrap_mode.unwrap_or_else(|| ui.wrap_mode());
126        let IntoSizedResult {
127            intrinsic_size,
128            sized,
129        } = self.kind.into_sized(
130            ui,
131            IntoSizedArgs {
132                available_size,
133                wrap_mode,
134                fallback_font,
135            },
136        );
137
138        let size = self
139            .size
140            .map_or_else(|| sized.size(), |s| s.at_most(self.max_size));
141
142        SizedAtom {
143            id,
144            size,
145            intrinsic_size: intrinsic_size.at_least(self.size.unwrap_or_default()),
146            grow: self.grow,
147            align: self.align,
148            kind: sized,
149        }
150    }
151}
152
153impl<'a, T> From<T> for Atom<'a>
154where
155    T: Into<AtomKind<'a>>,
156{
157    fn from(value: T) -> Self {
158        Atom {
159            kind: value.into(),
160            ..Default::default()
161        }
162    }
163}