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}