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