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