1use crate::{
2 AtomKind, AtomPaintArgs, Atoms, Direction, FontSelection, Frame, IdSalt, Image, IntoAtoms,
3 Response, SizedAtom, SizedAtomKind, Stroke, Ui, WidgetAtom,
4 text_selection::LabelSelectionState,
5};
6use alloc::sync::Arc;
7use core::ops::{Deref, DerefMut};
8use emath::{Align2, GuiRounding as _, NumExt as _, Rect, Vec2};
9use epaint::text::TextWrapMode;
10use epaint::{Color32, Galley};
11use smallvec::SmallVec;
12
13#[inline]
15fn main_cross_axis(direction: Direction) -> (usize, usize) {
16 let main = usize::from(!direction.is_horizontal());
17 (main, 1 - main)
18}
19
20#[inline]
22fn main_cross_vec(direction: Direction, main: f32, cross: f32) -> Vec2 {
23 if direction.is_horizontal() {
24 Vec2::new(main, cross)
25 } else {
26 Vec2::new(cross, main)
27 }
28}
29
30#[inline]
33fn main_cross_rect(direction: Direction, aligned_rect: Rect, min_main: f32, max_main: f32) -> Rect {
34 if direction.is_horizontal() {
35 Rect::from_x_y_ranges(min_main..=max_main, aligned_rect.y_range())
36 } else {
37 Rect::from_x_y_ranges(aligned_rect.x_range(), min_main..=max_main)
38 }
39}
40
41pub type CustomRects = SmallVec<[(IdSalt, Rect); 1]>;
45
46#[derive(Clone)]
64pub struct ContainerAtom<'a> {
65 pub atoms: Atoms<'a>,
66 gap: Option<f32>,
67 pub(crate) frame: Frame,
68 fallback_text_color: Option<Color32>,
69 fallback_font: Option<FontSelection>,
70 min_size: Vec2,
71 max_size: Vec2,
72 wrap_mode: Option<TextWrapMode>,
73 align2: Option<Align2>,
74 direction: Direction,
75}
76
77impl Default for ContainerAtom<'_> {
78 fn default() -> Self {
79 Self::new(())
80 }
81}
82
83impl<'a> ContainerAtom<'a> {
84 pub fn new(atoms: impl IntoAtoms<'a>) -> Self {
85 Self {
86 atoms: atoms.into_atoms(),
87 gap: None,
88 frame: Frame::default(),
89 fallback_text_color: None,
90 fallback_font: None,
91 min_size: Vec2::ZERO,
92 max_size: Vec2::INFINITY,
93 wrap_mode: None,
94 align2: None,
95 direction: Direction::LeftToRight,
96 }
97 }
98
99 pub fn measure(self, ui: &Ui, available_size: Vec2) -> SizedContainerAtom<'a> {
111 let Self {
112 mut atoms,
113 gap,
114 frame,
115 fallback_text_color,
116 min_size,
117 mut max_size,
118 wrap_mode,
119 align2,
120 fallback_font,
121 direction,
122 } = self;
123
124 let fallback_font = fallback_font.unwrap_or_default();
125
126 let wrap_mode = wrap_mode.unwrap_or_else(|| ui.wrap_mode());
127
128 if wrap_mode != TextWrapMode::Extend {
131 let any_shrink = atoms.any_shrink();
132 if !any_shrink {
133 let first_text = atoms
134 .iter_mut()
135 .find(|a| matches!(a.kind, AtomKind::Text(..)));
136 if let Some(atom) = first_text {
137 atom.shrink = true; }
139 }
140 }
141
142 let fallback_text_color =
143 fallback_text_color.unwrap_or_else(|| ui.style().visuals.text_color());
144 let gap = gap.unwrap_or_else(|| ui.spacing().icon_spacing);
145
146 if direction.is_horizontal() {
150 if ui.layout().horizontal_justify() {
151 max_size.x = f32::INFINITY;
152 }
153 } else if ui.layout().vertical_justify() {
154 max_size.y = f32::INFINITY;
155 }
156
157 let available_size = available_size.at_most(max_size).at_least(min_size);
158
159 let available_inner_size = available_size - frame.total_margin().sum();
161
162 let (main_axis, cross_axis) = main_cross_axis(direction);
167
168 let mut inner_main = 0.0;
169
170 let mut intrinsic_main = 0.0;
173 let mut intrinsic_cross: f32 = 0.0;
174
175 let mut cross_size: f32 = 0.0;
176
177 let mut sized_items = Vec::new();
178
179 let mut grow_count = 0;
180
181 let mut shrink_item = None;
182
183 let align2 = align2.unwrap_or_else(|| ui.layout().align2());
184
185 if atoms.len() > 1 {
186 let gap_space = gap * (atoms.len() as f32 - 1.0);
187 inner_main += gap_space;
188 intrinsic_main += gap_space;
189 }
190
191 let auto_id = ui.next_auto_id();
195
196 for (idx, mut item) in atoms.into_iter().enumerate() {
197 if let AtomKind::Widget(widget) = &mut item.kind {
198 widget.id.get_or_insert_with(|| auto_id.with(idx));
199 }
200 if item.grow {
201 grow_count += 1;
202 }
203 if item.shrink {
204 debug_assert!(
205 shrink_item.is_none(),
206 "Only one atomic may be marked as shrink. {item:?}"
207 );
208 if shrink_item.is_none() {
209 shrink_item = Some((idx, item));
210 continue;
211 }
212 }
213 let sized = item.into_sized(
214 ui,
215 available_inner_size,
216 Some(wrap_mode),
217 fallback_font.clone(),
218 );
219 let size = sized.size;
220
221 inner_main += size[main_axis];
222 intrinsic_main += sized.intrinsic_size[main_axis];
223
224 cross_size = cross_size.at_least(size[cross_axis]);
225 intrinsic_cross = intrinsic_cross.at_least(sized.intrinsic_size[cross_axis]);
226
227 sized_items.push(sized);
228 }
229
230 if let Some((index, item)) = shrink_item {
231 let available_size_for_shrink_item = main_cross_vec(
233 direction,
234 available_inner_size[main_axis] - inner_main,
235 available_inner_size[cross_axis],
236 );
237
238 let sized = item.into_sized(
239 ui,
240 available_size_for_shrink_item,
241 Some(wrap_mode),
242 fallback_font,
243 );
244 let size = sized.size;
245
246 inner_main += size[main_axis];
247 intrinsic_main += sized.intrinsic_size[main_axis];
248
249 cross_size = cross_size.at_least(size[cross_axis]);
250 intrinsic_cross = intrinsic_cross.at_least(sized.intrinsic_size[cross_axis]);
251
252 sized_items.insert(index, sized);
253 }
254
255 let margin = frame.total_margin();
256 let inner_size = main_cross_vec(direction, inner_main, cross_size);
257 let outer_size = (inner_size + margin.sum()).at_least(min_size);
258 let intrinsic_size = (main_cross_vec(direction, intrinsic_main, intrinsic_cross)
259 + margin.sum())
260 .at_least(min_size);
261
262 SizedContainerAtom {
263 sized_atoms: sized_items,
264 frame,
265 fallback_text_color,
266 outer_size,
267 intrinsic_size,
268 grow_count,
269 inner_size,
270 align2,
271 gap,
272 direction,
273 }
274 }
275}
276
277macro_rules! shared_container_builders {
284 (
285 $(
286 $(#[$meta:meta])*
287
288 fn $name:ident($self:ident, $($arg:ident: $arg_ty:ty),* $(,)?) $body:block
289 )*
290 ) => {
291 impl<'a> ContainerAtom<'a> {
292 $(
293 $(#[$meta])*
294 #[inline]
295 pub fn $name(mut $self, $($arg: $arg_ty),*) -> Self {
296 $body
297 $self
298 }
299 )*
300 }
301
302 impl<'a> WidgetAtom<'a> {
303 $(
304 $(#[$meta])*
305 #[inline]
306 pub fn $name(mut self, $($arg: $arg_ty),*) -> Self {
307 self.container = self.container.$name($($arg),*);
308 self
309 }
310 )*
311 }
312 };
313}
314
315shared_container_builders! {
316 fn gap(self, gap: f32) {
320 self.gap = Some(gap);
321 }
322
323 fn fallback_gap(self, gap: f32) {
325 self.gap = self.gap.or(Some(gap));
326 }
327
328 fn direction(self, direction: Direction) {
330 self.direction = direction;
331 }
332
333 fn frame(self, frame: Frame) {
335 self.frame = frame;
336 }
337
338 fn fallback_text_color(self, color: Color32) {
342 self.fallback_text_color = Some(color);
343 }
344
345 fn fallback_font(self, font: impl Into<FontSelection>) {
347 self.fallback_font = Some(font.into());
348 }
349
350 fn min_size(self, size: Vec2) {
355 self.min_size = size;
356 }
357
358 fn max_size(self, size: Vec2) {
362 self.max_size = size;
363 }
364
365 fn max_width(self, width: f32) {
369 self.max_size.x = width;
370 }
371
372 fn max_height(self, height: f32) {
376 self.max_size.y = height;
377 }
378
379 fn wrap_mode(self, wrap_mode: TextWrapMode) {
385 self.wrap_mode = Some(wrap_mode);
386 }
387
388 fn align2(self, align2: Align2) {
396 self.align2 = Some(align2);
397 }
398}
399
400#[derive(Clone, Debug)]
407pub struct SizedContainerAtom<'a> {
408 pub(crate) outer_size: Vec2,
412
413 inner_size: Vec2,
415
416 sized_atoms: Vec<SizedAtom<'a>>,
418
419 pub frame: Frame,
421
422 pub fallback_text_color: Color32,
424
425 pub(crate) intrinsic_size: Vec2,
428
429 grow_count: usize,
431
432 align2: Align2,
434
435 gap: f32,
437
438 direction: Direction,
440}
441
442impl<'atom> SizedContainerAtom<'atom> {
443 pub fn iter_kinds(&self) -> impl Iterator<Item = &SizedAtomKind<'atom>> {
444 self.sized_atoms.iter().map(|atom| &atom.kind)
445 }
446
447 pub fn iter_kinds_mut(&mut self) -> impl Iterator<Item = &mut SizedAtomKind<'atom>> {
448 self.sized_atoms.iter_mut().map(|atom| &mut atom.kind)
449 }
450
451 pub fn iter_images(&self) -> impl Iterator<Item = &Image<'atom>> {
452 self.iter_kinds().filter_map(|kind| {
453 if let SizedAtomKind::Image { image, size: _ } = kind {
454 Some(image)
455 } else {
456 None
457 }
458 })
459 }
460
461 pub fn iter_images_mut(&mut self) -> impl Iterator<Item = &mut Image<'atom>> {
462 self.iter_kinds_mut().filter_map(|kind| {
463 if let SizedAtomKind::Image { image, size: _ } = kind {
464 Some(image)
465 } else {
466 None
467 }
468 })
469 }
470
471 pub fn iter_texts(&self) -> impl Iterator<Item = &Arc<Galley>> + use<'atom, '_> {
472 self.iter_kinds().filter_map(|kind| {
473 if let SizedAtomKind::Text(text) = kind {
474 Some(text)
475 } else {
476 None
477 }
478 })
479 }
480
481 pub fn iter_texts_mut(&mut self) -> impl Iterator<Item = &mut Arc<Galley>> + use<'atom, '_> {
482 self.iter_kinds_mut().filter_map(|kind| {
483 if let SizedAtomKind::Text(text) = kind {
484 Some(text)
485 } else {
486 None
487 }
488 })
489 }
490
491 pub fn map_kind<F>(&mut self, mut f: F)
492 where
493 F: FnMut(SizedAtomKind<'atom>) -> SizedAtomKind<'atom>,
494 {
495 for kind in self.iter_kinds_mut() {
496 *kind = f(core::mem::take(kind));
497 }
498 }
499
500 pub fn map_images<F>(&mut self, mut f: F)
501 where
502 F: FnMut(Image<'atom>) -> Image<'atom>,
503 {
504 self.map_kind(|kind| {
505 if let SizedAtomKind::Image { image, size } = kind {
506 SizedAtomKind::Image {
507 image: f(image),
508 size,
509 }
510 } else {
511 kind
512 }
513 });
514 }
515
516 pub fn paint_at(self, ui: &Ui, rect: Rect) -> CustomRects {
524 self.paint_at_with_selection(ui, rect, None)
525 }
526
527 pub(crate) fn paint_at_with_selection(
528 self,
529 ui: &Ui,
530 rect: Rect,
531 selection_response: Option<&Response>,
532 ) -> CustomRects {
533 let Self {
534 mut sized_atoms,
535 frame,
536 fallback_text_color,
537 grow_count,
538 inner_size,
539 align2,
540 gap,
541 direction,
542 ..
543 } = self;
544
545 let inner_rect = rect - frame.total_margin();
546
547 ui.painter().add(frame.paint(inner_rect));
548
549 let (main_axis, cross_axis) = main_cross_axis(direction);
550
551 let main_to_fill = inner_rect.size()[main_axis];
553 let inner_main = inner_size[main_axis];
554 let extra_space = f32::max(main_to_fill - inner_main, 0.0);
555 let grow_main = f32::max(extra_space / grow_count as f32, 0.0).floor_ui();
556
557 let block_main = if grow_count > 0 {
560 main_to_fill
561 } else {
562 inner_main
563 };
564 let block_size = main_cross_vec(direction, block_main, inner_size[cross_axis]);
565 let aligned_rect = align2.align_size_within_rect(block_size, inner_rect);
566
567 if matches!(direction, Direction::RightToLeft | Direction::BottomUp) {
570 sized_atoms.reverse();
571 }
572
573 let mut cursor = aligned_rect.min.to_vec2()[main_axis];
575
576 let mut custom_rects = CustomRects::new();
577
578 for sized in sized_atoms {
579 let size = sized.size;
580 let growth = if sized.is_grow() { grow_main } else { 0.0 };
583
584 let atom_main = size[main_axis] + growth;
585
586 let cell = main_cross_rect(direction, aligned_rect, cursor, cursor + atom_main);
588 cursor += atom_main + gap;
589 let item_rect = sized.align.align_size_within_rect(size, cell);
590
591 if let Some(id) = sized.id {
592 debug_assert!(
593 !custom_rects.iter().any(|(i, _)| *i == id),
594 "Duplicate custom id"
595 );
596 custom_rects.push((id, item_rect));
597 }
598
599 match sized.kind {
600 SizedAtomKind::Text(galley) => {
601 if let Some(response) = selection_response {
602 LabelSelectionState::label_text_selection(
606 ui,
607 response,
608 item_rect.min,
609 galley,
610 fallback_text_color,
611 Stroke::NONE,
612 );
613 } else {
614 ui.painter()
615 .galley(item_rect.min, galley, fallback_text_color);
616 }
617 }
618 SizedAtomKind::Image { image, size: _ } => {
619 image.paint_at(ui, item_rect);
620 }
621 SizedAtomKind::Empty { .. } => {}
622 SizedAtomKind::Paint { paint, size: _ } => {
623 paint(
624 ui,
625 AtomPaintArgs {
626 rect: item_rect,
627 fallback_text_color,
628 },
629 );
630 }
631 SizedAtomKind::Widget(widget) => {
632 widget.paint_at(ui, cell);
634 }
635 SizedAtomKind::Container(container) => {
636 container.paint_at(ui, cell);
638 }
639 }
640 }
641
642 custom_rects
643 }
644}
645
646impl<'a> Deref for ContainerAtom<'a> {
647 type Target = Atoms<'a>;
648
649 fn deref(&self) -> &Self::Target {
650 &self.atoms
651 }
652}
653
654impl DerefMut for ContainerAtom<'_> {
655 fn deref_mut(&mut self) -> &mut Self::Target {
656 &mut self.atoms
657 }
658}
659
660impl<'a> Deref for SizedContainerAtom<'a> {
661 type Target = [SizedAtom<'a>];
662
663 fn deref(&self) -> &Self::Target {
664 &self.sized_atoms
665 }
666}
667
668impl DerefMut for SizedContainerAtom<'_> {
669 fn deref_mut(&mut self) -> &mut Self::Target {
670 &mut self.sized_atoms
671 }
672}