1use crate::atomics::ATOMS_SMALL_VEC_SIZE;
2use crate::{
3 AtomKind, Atoms, Frame, Id, Image, IntoAtoms, Response, Sense, SizedAtom, SizedAtomKind, Ui,
4 Widget,
5};
6use emath::{Align2, GuiRounding as _, NumExt as _, Rect, Vec2};
7use epaint::text::TextWrapMode;
8use epaint::{Color32, Galley};
9use smallvec::SmallVec;
10use std::ops::{Deref, DerefMut};
11use std::sync::Arc;
12
13pub struct AtomLayout<'a> {
33 id: Option<Id>,
34 pub atoms: Atoms<'a>,
35 gap: Option<f32>,
36 pub(crate) frame: Frame,
37 pub(crate) sense: Sense,
38 fallback_text_color: Option<Color32>,
39 min_size: Vec2,
40 wrap_mode: Option<TextWrapMode>,
41 align2: Option<Align2>,
42}
43
44impl Default for AtomLayout<'_> {
45 fn default() -> Self {
46 Self::new(())
47 }
48}
49
50impl<'a> AtomLayout<'a> {
51 pub fn new(atoms: impl IntoAtoms<'a>) -> Self {
52 Self {
53 id: None,
54 atoms: atoms.into_atoms(),
55 gap: None,
56 frame: Frame::default(),
57 sense: Sense::hover(),
58 fallback_text_color: None,
59 min_size: Vec2::ZERO,
60 wrap_mode: None,
61 align2: None,
62 }
63 }
64
65 #[inline]
69 pub fn gap(mut self, gap: f32) -> Self {
70 self.gap = Some(gap);
71 self
72 }
73
74 #[inline]
76 pub fn frame(mut self, frame: Frame) -> Self {
77 self.frame = frame;
78 self
79 }
80
81 #[inline]
83 pub fn sense(mut self, sense: Sense) -> Self {
84 self.sense = sense;
85 self
86 }
87
88 #[inline]
92 pub fn fallback_text_color(mut self, color: Color32) -> Self {
93 self.fallback_text_color = Some(color);
94 self
95 }
96
97 #[inline]
102 pub fn min_size(mut self, size: Vec2) -> Self {
103 self.min_size = size;
104 self
105 }
106
107 #[inline]
109 pub fn id(mut self, id: Id) -> Self {
110 self.id = Some(id);
111 self
112 }
113
114 #[inline]
120 pub fn wrap_mode(mut self, wrap_mode: TextWrapMode) -> Self {
121 self.wrap_mode = Some(wrap_mode);
122 self
123 }
124
125 #[inline]
133 pub fn align2(mut self, align2: Align2) -> Self {
134 self.align2 = Some(align2);
135 self
136 }
137
138 pub fn show(self, ui: &mut Ui) -> AtomLayoutResponse {
140 self.allocate(ui).paint(ui)
141 }
142
143 pub fn allocate(self, ui: &mut Ui) -> AllocatedAtomLayout<'a> {
147 let Self {
148 id,
149 mut atoms,
150 gap,
151 frame,
152 sense,
153 fallback_text_color,
154 min_size,
155 wrap_mode,
156 align2,
157 } = self;
158
159 let wrap_mode = wrap_mode.unwrap_or(ui.wrap_mode());
160
161 if wrap_mode != TextWrapMode::Extend {
164 let any_shrink = atoms.iter().any(|a| a.shrink);
165 if !any_shrink {
166 let first_text = atoms
167 .iter_mut()
168 .find(|a| matches!(a.kind, AtomKind::Text(..)));
169 if let Some(atom) = first_text {
170 atom.shrink = true; }
172 }
173 }
174
175 let id = id.unwrap_or_else(|| ui.next_auto_id());
176
177 let fallback_text_color =
178 fallback_text_color.unwrap_or_else(|| ui.style().visuals.text_color());
179 let gap = gap.unwrap_or(ui.spacing().icon_spacing);
180
181 let available_inner_size = ui.available_size() - frame.total_margin().sum();
183
184 let mut desired_width = 0.0;
185
186 let mut intrinsic_width = 0.0;
189 let mut intrinsic_height = 0.0;
190
191 let mut height: f32 = 0.0;
192
193 let mut sized_items = SmallVec::new();
194
195 let mut grow_count = 0;
196
197 let mut shrink_item = None;
198
199 let align2 = align2.unwrap_or_else(|| {
200 Align2([ui.layout().horizontal_align(), ui.layout().vertical_align()])
201 });
202
203 if atoms.len() > 1 {
204 let gap_space = gap * (atoms.len() as f32 - 1.0);
205 desired_width += gap_space;
206 intrinsic_width += gap_space;
207 }
208
209 for (idx, item) in atoms.into_iter().enumerate() {
210 if item.grow {
211 grow_count += 1;
212 }
213 if item.shrink {
214 debug_assert!(
215 shrink_item.is_none(),
216 "Only one atomic may be marked as shrink. {item:?}"
217 );
218 if shrink_item.is_none() {
219 shrink_item = Some((idx, item));
220 continue;
221 }
222 }
223 let sized = item.into_sized(ui, available_inner_size, Some(wrap_mode));
224 let size = sized.size;
225
226 desired_width += size.x;
227 intrinsic_width += sized.intrinsic_size.x;
228
229 height = height.at_least(size.y);
230 intrinsic_height = intrinsic_height.at_least(sized.intrinsic_size.y);
231
232 sized_items.push(sized);
233 }
234
235 if let Some((index, item)) = shrink_item {
236 let available_size_for_shrink_item = Vec2::new(
238 available_inner_size.x - desired_width,
239 available_inner_size.y,
240 );
241
242 let sized = item.into_sized(ui, available_size_for_shrink_item, Some(wrap_mode));
243 let size = sized.size;
244
245 desired_width += size.x;
246 intrinsic_width += sized.intrinsic_size.x;
247
248 height = height.at_least(size.y);
249 intrinsic_height = intrinsic_height.at_least(sized.intrinsic_size.y);
250
251 sized_items.insert(index, sized);
252 }
253
254 let margin = frame.total_margin();
255 let desired_size = Vec2::new(desired_width, height);
256 let frame_size = (desired_size + margin.sum()).at_least(min_size);
257
258 let (_, rect) = ui.allocate_space(frame_size);
259 let mut response = ui.interact(rect, id, sense);
260
261 response.intrinsic_size =
262 Some((Vec2::new(intrinsic_width, intrinsic_height) + margin.sum()).at_least(min_size));
263
264 AllocatedAtomLayout {
265 sized_atoms: sized_items,
266 frame,
267 fallback_text_color,
268 response,
269 grow_count,
270 desired_size,
271 align2,
272 gap,
273 }
274 }
275}
276
277#[derive(Clone, Debug)]
279pub struct AllocatedAtomLayout<'a> {
280 pub sized_atoms: SmallVec<[SizedAtom<'a>; ATOMS_SMALL_VEC_SIZE]>,
281 pub frame: Frame,
282 pub fallback_text_color: Color32,
283 pub response: Response,
284 grow_count: usize,
285 desired_size: Vec2,
287 align2: Align2,
288 gap: f32,
289}
290
291impl<'atom> AllocatedAtomLayout<'atom> {
292 pub fn iter_kinds(&self) -> impl Iterator<Item = &SizedAtomKind<'atom>> {
293 self.sized_atoms.iter().map(|atom| &atom.kind)
294 }
295
296 pub fn iter_kinds_mut(&mut self) -> impl Iterator<Item = &mut SizedAtomKind<'atom>> {
297 self.sized_atoms.iter_mut().map(|atom| &mut atom.kind)
298 }
299
300 pub fn iter_images(&self) -> impl Iterator<Item = &Image<'atom>> {
301 self.iter_kinds().filter_map(|kind| {
302 if let SizedAtomKind::Image(image, _) = kind {
303 Some(image)
304 } else {
305 None
306 }
307 })
308 }
309
310 pub fn iter_images_mut(&mut self) -> impl Iterator<Item = &mut Image<'atom>> {
311 self.iter_kinds_mut().filter_map(|kind| {
312 if let SizedAtomKind::Image(image, _) = kind {
313 Some(image)
314 } else {
315 None
316 }
317 })
318 }
319
320 pub fn iter_texts(&self) -> impl Iterator<Item = &Arc<Galley>> + use<'atom, '_> {
321 self.iter_kinds().filter_map(|kind| {
322 if let SizedAtomKind::Text(text) = kind {
323 Some(text)
324 } else {
325 None
326 }
327 })
328 }
329
330 pub fn iter_texts_mut(&mut self) -> impl Iterator<Item = &mut Arc<Galley>> + use<'atom, '_> {
331 self.iter_kinds_mut().filter_map(|kind| {
332 if let SizedAtomKind::Text(text) = kind {
333 Some(text)
334 } else {
335 None
336 }
337 })
338 }
339
340 pub fn map_kind<F>(&mut self, mut f: F)
341 where
342 F: FnMut(SizedAtomKind<'atom>) -> SizedAtomKind<'atom>,
343 {
344 for kind in self.iter_kinds_mut() {
345 *kind = f(std::mem::take(kind));
346 }
347 }
348
349 pub fn map_images<F>(&mut self, mut f: F)
350 where
351 F: FnMut(Image<'atom>) -> Image<'atom>,
352 {
353 self.map_kind(|kind| {
354 if let SizedAtomKind::Image(image, size) = kind {
355 SizedAtomKind::Image(f(image), size)
356 } else {
357 kind
358 }
359 });
360 }
361
362 pub fn paint(self, ui: &Ui) -> AtomLayoutResponse {
364 let Self {
365 sized_atoms,
366 frame,
367 fallback_text_color,
368 response,
369 grow_count,
370 desired_size,
371 align2,
372 gap,
373 } = self;
374
375 let inner_rect = response.rect - self.frame.total_margin();
376
377 ui.painter().add(frame.paint(inner_rect));
378
379 let width_to_fill = inner_rect.width();
380 let extra_space = f32::max(width_to_fill - desired_size.x, 0.0);
381 let grow_width = f32::max(extra_space / grow_count as f32, 0.0).floor_ui();
382
383 let aligned_rect = if grow_count > 0 {
384 align2.align_size_within_rect(Vec2::new(width_to_fill, desired_size.y), inner_rect)
385 } else {
386 align2.align_size_within_rect(desired_size, inner_rect)
387 };
388
389 let mut cursor = aligned_rect.left();
390
391 let mut response = AtomLayoutResponse::empty(response);
392
393 for sized in sized_atoms {
394 let size = sized.size;
395 let growth = if sized.is_grow() { grow_width } else { 0.0 };
398
399 let frame = aligned_rect
400 .with_min_x(cursor)
401 .with_max_x(cursor + size.x + growth);
402 cursor = frame.right() + gap;
403
404 let align = Align2::CENTER_CENTER;
405 let rect = align.align_size_within_rect(size, frame);
406
407 match sized.kind {
408 SizedAtomKind::Text(galley) => {
409 ui.painter().galley(rect.min, galley, fallback_text_color);
410 }
411 SizedAtomKind::Image(image, _) => {
412 image.paint_at(ui, rect);
413 }
414 SizedAtomKind::Custom(id) => {
415 debug_assert!(
416 !response.custom_rects.iter().any(|(i, _)| *i == id),
417 "Duplicate custom id"
418 );
419 response.custom_rects.push((id, rect));
420 }
421 SizedAtomKind::Empty => {}
422 }
423 }
424
425 response
426 }
427}
428
429#[derive(Clone, Debug)]
433pub struct AtomLayoutResponse {
434 pub response: Response,
435 custom_rects: SmallVec<[(Id, Rect); 1]>,
437}
438
439impl AtomLayoutResponse {
440 pub fn empty(response: Response) -> Self {
441 Self {
442 response,
443 custom_rects: Default::default(),
444 }
445 }
446
447 pub fn custom_rects(&self) -> impl Iterator<Item = (Id, Rect)> + '_ {
448 self.custom_rects.iter().copied()
449 }
450
451 pub fn rect(&self, id: Id) -> Option<Rect> {
455 self.custom_rects
456 .iter()
457 .find_map(|(i, r)| if *i == id { Some(*r) } else { None })
458 }
459}
460
461impl Widget for AtomLayout<'_> {
462 fn ui(self, ui: &mut Ui) -> Response {
463 self.show(ui).response
464 }
465}
466
467impl<'a> Deref for AtomLayout<'a> {
468 type Target = Atoms<'a>;
469
470 fn deref(&self) -> &Self::Target {
471 &self.atoms
472 }
473}
474
475impl DerefMut for AtomLayout<'_> {
476 fn deref_mut(&mut self) -> &mut Self::Target {
477 &mut self.atoms
478 }
479}
480
481impl<'a> Deref for AllocatedAtomLayout<'a> {
482 type Target = [SizedAtom<'a>];
483
484 fn deref(&self) -> &Self::Target {
485 &self.sized_atoms
486 }
487}
488
489impl DerefMut for AllocatedAtomLayout<'_> {
490 fn deref_mut(&mut self) -> &mut Self::Target {
491 &mut self.sized_atoms
492 }
493}