Skip to main content

egui/atomics/
atom.rs

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