egui/atomics/atom_kind.rs
1use crate::{AtomLayout, FontSelection, Image, ImageSource, SizedAtomKind, Ui, WidgetText};
2use core::fmt::Debug;
3use emath::Vec2;
4use epaint::text::TextWrapMode;
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 /// A nested [`AtomLayout`], letting you embed an atom-based widget as a single atom
70 /// inside another [`AtomLayout`].
71 ///
72 /// The nested layout is measured (sized) when the parent is sized, and painted (and
73 /// interacted with) at the cell rect the parent computes for it.
74 Layout(Box<AtomLayout<'a>>),
75}
76
77impl Clone for AtomKind<'_> {
78 fn clone(&self) -> Self {
79 match self {
80 AtomKind::Empty => AtomKind::Empty,
81 AtomKind::Text(text) => AtomKind::Text(text.clone()),
82 AtomKind::Image(image) => AtomKind::Image(image.clone()),
83 AtomKind::Closure(_) => {
84 log::warn!("Cannot clone atom closures");
85 AtomKind::Empty
86 }
87 AtomKind::Layout(layout) => AtomKind::Layout(layout.clone()),
88 }
89 }
90}
91
92impl Debug for AtomKind<'_> {
93 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
94 match self {
95 AtomKind::Empty => write!(f, "AtomKind::Empty"),
96 AtomKind::Text(text) => write!(f, "AtomKind::Text({text:?})"),
97 AtomKind::Image(image) => write!(f, "AtomKind::Image({image:?})"),
98 AtomKind::Closure(_) => write!(f, "AtomKind::Closure(<closure>)"),
99 AtomKind::Layout(_) => write!(f, "AtomKind::Layout(<layout>)"),
100 }
101 }
102}
103
104impl<'a> AtomKind<'a> {
105 /// See [`Self::Text`]
106 pub fn text(text: impl Into<WidgetText>) -> Self {
107 AtomKind::Text(text.into())
108 }
109
110 /// See [`Self::Image`]
111 pub fn image(image: impl Into<Image<'a>>) -> Self {
112 AtomKind::Image(image.into())
113 }
114
115 /// See [`Self::Closure`]
116 pub fn closure(func: impl FnOnce(&Ui, IntoSizedArgs) -> IntoSizedResult<'static> + 'a) -> Self {
117 AtomKind::Closure(Box::new(func))
118 }
119
120 /// Turn this [`AtomKind`] into a [`SizedAtomKind`].
121 ///
122 /// This converts [`WidgetText`] into [`crate::Galley`] and tries to load and size [`Image`].
123 /// The first returned argument is the preferred size.
124 pub fn into_sized(
125 self,
126 ui: &Ui,
127 IntoSizedArgs {
128 available_size,
129 wrap_mode,
130 fallback_font,
131 }: IntoSizedArgs,
132 ) -> IntoSizedResult<'a> {
133 match self {
134 AtomKind::Text(text) => {
135 let galley = text.into_galley(ui, Some(wrap_mode), available_size.x, fallback_font);
136 IntoSizedResult {
137 intrinsic_size: galley.intrinsic_size(),
138 sized: SizedAtomKind::Text(galley),
139 }
140 }
141 AtomKind::Image(image) => {
142 let size = image.load_and_calc_size(ui, available_size);
143 let size = size.unwrap_or(Vec2::ZERO);
144 IntoSizedResult {
145 intrinsic_size: size,
146 sized: SizedAtomKind::Image { image, size },
147 }
148 }
149 AtomKind::Empty => IntoSizedResult {
150 intrinsic_size: Vec2::ZERO,
151 sized: SizedAtomKind::Empty { size: None },
152 },
153 AtomKind::Closure(func) => func(
154 ui,
155 IntoSizedArgs {
156 available_size,
157 wrap_mode,
158 fallback_font,
159 },
160 ),
161 AtomKind::Layout(layout) => {
162 let sized = layout.measure(ui, available_size);
163 IntoSizedResult {
164 intrinsic_size: sized.intrinsic_size,
165 sized: SizedAtomKind::Layout(Box::new(sized)),
166 }
167 }
168 }
169 }
170}
171
172impl<'a> From<ImageSource<'a>> for AtomKind<'a> {
173 fn from(value: ImageSource<'a>) -> Self {
174 AtomKind::Image(value.into())
175 }
176}
177
178impl<'a> From<Image<'a>> for AtomKind<'a> {
179 fn from(value: Image<'a>) -> Self {
180 AtomKind::Image(value)
181 }
182}
183
184impl<T> From<T> for AtomKind<'_>
185where
186 T: Into<WidgetText>,
187{
188 fn from(value: T) -> Self {
189 AtomKind::Text(value.into())
190 }
191}
192
193impl<'a> From<AtomLayout<'a>> for AtomKind<'a> {
194 fn from(layout: AtomLayout<'a>) -> Self {
195 AtomKind::Layout(Box::new(layout))
196 }
197}