egui/atomics/
atom_kind.rs

1use crate::{FontSelection, Image, ImageSource, SizedAtomKind, Ui, WidgetText};
2use emath::Vec2;
3use epaint::text::TextWrapMode;
4use std::fmt::Debug;
5
6/// Args passed when sizing an [`super::Atom`]
7pub struct IntoSizedArgs {
8    pub available_size: Vec2,
9    pub wrap_mode: TextWrapMode,
10    pub fallback_font: FontSelection,
11}
12
13/// Result returned when sizing an [`super::Atom`]
14pub struct IntoSizedResult<'a> {
15    pub intrinsic_size: Vec2,
16    pub sized: SizedAtomKind<'a>,
17}
18
19/// See [`AtomKind::Closure`]
20// We need 'static in the result (or need to introduce another lifetime on the enum).
21// Otherwise, a single 'static Atom would force the closure to be 'static.
22pub type AtomClosure<'a> = Box<dyn FnOnce(&Ui, IntoSizedArgs) -> IntoSizedResult<'static> + 'a>;
23
24/// The different kinds of [`crate::Atom`]s.
25#[derive(Default)]
26pub enum AtomKind<'a> {
27    /// Empty, that can be used with [`crate::AtomExt::atom_grow`] to reserve space.
28    #[default]
29    Empty,
30
31    /// Text atom.
32    ///
33    /// Truncation within [`crate::AtomLayout`] works like this:
34    /// -
35    /// - if `wrap_mode` is not Extend
36    ///   - if no atom is `shrink`
37    ///     - the first text atom is selected and will be marked as `shrink`
38    ///   - the atom marked as `shrink` will shrink / wrap based on the selected wrap mode
39    ///   - any other text atoms will have `wrap_mode` extend
40    /// - if `wrap_mode` is extend, Text will extend as expected.
41    ///
42    /// Unless [`crate::AtomExt::atom_max_width`] is set, `wrap_mode` should only be set via [`crate::Style`] or
43    /// [`crate::AtomLayout::wrap_mode`], as setting a wrap mode on a [`WidgetText`] atom
44    /// that is not `shrink` will have unexpected results.
45    ///
46    /// The size is determined by converting the [`WidgetText`] into a galley and using the galleys
47    /// size. You can use [`crate::AtomExt::atom_size`] to override this, and [`crate::AtomExt::atom_max_width`]
48    /// to limit the width (Causing the text to wrap or truncate, depending on the `wrap_mode`.
49    /// [`crate::AtomExt::atom_max_height`] has no effect on text.
50    Text(WidgetText),
51
52    /// Image atom.
53    ///
54    /// By default the size is determined via [`Image::calc_size`].
55    /// You can use [`crate::AtomExt::atom_max_size`] or [`crate::AtomExt::atom_size`] to customize the size.
56    /// There is also a helper [`crate::AtomExt::atom_max_height_font_size`] to set the max height to the
57    /// default font height, which is convenient for icons.
58    Image(Image<'a>),
59
60    /// A custom closure that produces a sized atom.
61    ///
62    /// The vec2 passed in is the available size to this atom. The returned vec2 should be the
63    /// preferred / intrinsic size.
64    ///
65    /// Note: This api is experimental, expect breaking changes here.
66    /// When cloning, this will be cloned as [`AtomKind::Empty`].
67    Closure(AtomClosure<'a>),
68}
69
70impl Clone for AtomKind<'_> {
71    fn clone(&self) -> Self {
72        match self {
73            AtomKind::Empty => AtomKind::Empty,
74            AtomKind::Text(text) => AtomKind::Text(text.clone()),
75            AtomKind::Image(image) => AtomKind::Image(image.clone()),
76            AtomKind::Closure(_) => {
77                log::warn!("Cannot clone atom closures");
78                AtomKind::Empty
79            }
80        }
81    }
82}
83
84impl Debug for AtomKind<'_> {
85    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86        match self {
87            AtomKind::Empty => write!(f, "AtomKind::Empty"),
88            AtomKind::Text(text) => write!(f, "AtomKind::Text({text:?})"),
89            AtomKind::Image(image) => write!(f, "AtomKind::Image({image:?})"),
90            AtomKind::Closure(_) => write!(f, "AtomKind::Closure(<closure>)"),
91        }
92    }
93}
94
95impl<'a> AtomKind<'a> {
96    /// See [`Self::Text`]
97    pub fn text(text: impl Into<WidgetText>) -> Self {
98        AtomKind::Text(text.into())
99    }
100
101    /// See [`Self::Image`]
102    pub fn image(image: impl Into<Image<'a>>) -> Self {
103        AtomKind::Image(image.into())
104    }
105
106    /// See [`Self::Closure`]
107    pub fn closure(func: impl FnOnce(&Ui, IntoSizedArgs) -> IntoSizedResult<'static> + 'a) -> Self {
108        AtomKind::Closure(Box::new(func))
109    }
110
111    /// Turn this [`AtomKind`] into a [`SizedAtomKind`].
112    ///
113    /// This converts [`WidgetText`] into [`crate::Galley`] and tries to load and size [`Image`].
114    /// The first returned argument is the preferred size.
115    pub fn into_sized(
116        self,
117        ui: &Ui,
118        IntoSizedArgs {
119            available_size,
120            wrap_mode,
121            fallback_font,
122        }: IntoSizedArgs,
123    ) -> IntoSizedResult<'a> {
124        match self {
125            AtomKind::Text(text) => {
126                let galley = text.into_galley(ui, Some(wrap_mode), available_size.x, fallback_font);
127                IntoSizedResult {
128                    intrinsic_size: galley.intrinsic_size(),
129                    sized: SizedAtomKind::Text(galley),
130                }
131            }
132            AtomKind::Image(image) => {
133                let size = image.load_and_calc_size(ui, available_size);
134                let size = size.unwrap_or(Vec2::ZERO);
135                IntoSizedResult {
136                    intrinsic_size: size,
137                    sized: SizedAtomKind::Image { image, size },
138                }
139            }
140            AtomKind::Empty => IntoSizedResult {
141                intrinsic_size: Vec2::ZERO,
142                sized: SizedAtomKind::Empty { size: None },
143            },
144            AtomKind::Closure(func) => func(
145                ui,
146                IntoSizedArgs {
147                    available_size,
148                    wrap_mode,
149                    fallback_font,
150                },
151            ),
152        }
153    }
154}
155
156impl<'a> From<ImageSource<'a>> for AtomKind<'a> {
157    fn from(value: ImageSource<'a>) -> Self {
158        AtomKind::Image(value.into())
159    }
160}
161
162impl<'a> From<Image<'a>> for AtomKind<'a> {
163    fn from(value: Image<'a>) -> Self {
164        AtomKind::Image(value)
165    }
166}
167
168impl<T> From<T> for AtomKind<'_>
169where
170    T: Into<WidgetText>,
171{
172    fn from(value: T) -> Self {
173        AtomKind::Text(value.into())
174    }
175}