1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-26 22:53:14 -04:00

Add AtomKind::Layout (allow nesting)

This commit is contained in:
lucasmerlin
2026-06-02 11:21:13 +02:00
parent d9e7f6b2ed
commit 583bfc94d3
5 changed files with 69 additions and 15 deletions

View File

@@ -1,4 +1,6 @@
use crate::{AtomKind, FontSelection, Id, IntoSizedArgs, IntoSizedResult, SizedAtom, Ui};
use crate::{
AtomKind, AtomLayout, FontSelection, Id, IntoSizedArgs, IntoSizedResult, SizedAtom, Ui,
};
use emath::{Align2, NumExt as _, Vec2};
use epaint::text::TextWrapMode;
@@ -101,6 +103,17 @@ impl<'a> Atom<'a> {
}
}
/// Nest an [`AtomLayout`] (e.g. an atom-based widget) as a single atom.
///
/// The nested layout is sized when the parent is sized and painted (and interacted with)
/// at the cell the parent computes for it. See [`AtomKind::Layout`].
pub fn layout(layout: AtomLayout<'a>) -> Self {
Atom {
kind: AtomKind::Layout(Box::new(layout)),
..Default::default()
}
}
/// Turn this into a [`SizedAtom`].
pub fn into_sized(
self,
@@ -161,3 +174,12 @@ where
}
}
}
// Note: this is a concrete `From` (not a blanket `From<impl Into<AtomKind>>`) on purpose.
// `AtomLayout` must NOT implement `Into<AtomKind>`, or this would conflict with the blanket impl
// above. Keep nesting going through `AtomKind::Layout` / `Atom::layout` only.
impl<'a> From<AtomLayout<'a>> for Atom<'a> {
fn from(layout: AtomLayout<'a>) -> Self {
Atom::layout(layout)
}
}

View File

@@ -1,4 +1,4 @@
use crate::{FontSelection, Image, ImageSource, SizedAtomKind, Ui, WidgetText};
use crate::{AtomLayout, FontSelection, Image, ImageSource, SizedAtomKind, Ui, WidgetText};
use emath::Vec2;
use epaint::text::TextWrapMode;
use std::fmt::Debug;
@@ -65,6 +65,13 @@ pub enum AtomKind<'a> {
/// Note: This api is experimental, expect breaking changes here.
/// When cloning, this will be cloned as [`AtomKind::Empty`].
Closure(AtomClosure<'a>),
/// A nested [`AtomLayout`], letting you embed an atom-based widget as a single atom
/// inside another [`AtomLayout`].
///
/// The nested layout is measured (sized) when the parent is sized, and painted (and
/// interacted with) at the cell rect the parent computes for it.
Layout(Box<AtomLayout<'a>>),
}
impl Clone for AtomKind<'_> {
@@ -77,6 +84,7 @@ impl Clone for AtomKind<'_> {
log::warn!("Cannot clone atom closures");
AtomKind::Empty
}
AtomKind::Layout(layout) => AtomKind::Layout(layout.clone()),
}
}
}
@@ -88,6 +96,7 @@ impl Debug for AtomKind<'_> {
AtomKind::Text(text) => write!(f, "AtomKind::Text({text:?})"),
AtomKind::Image(image) => write!(f, "AtomKind::Image({image:?})"),
AtomKind::Closure(_) => write!(f, "AtomKind::Closure(<closure>)"),
AtomKind::Layout(_) => write!(f, "AtomKind::Layout(<layout>)"),
}
}
}
@@ -149,6 +158,15 @@ impl<'a> AtomKind<'a> {
fallback_font,
},
),
AtomKind::Layout(layout) => {
// The nested layout is self-contained: it resolves its own wrap mode, fallback
// font and frame, so we only forward the available size.
let sized = layout.measure(ui, available_size);
IntoSizedResult {
intrinsic_size: sized.intrinsic_size,
sized: SizedAtomKind::Layout(Box::new(sized)),
}
}
}
}
}

View File

@@ -1,4 +1,3 @@
use crate::atomics::ATOMS_SMALL_VEC_SIZE;
use crate::{
AtomKind, Atoms, FontSelection, Frame, Id, Image, IntoAtoms, Response, Sense, SizedAtom,
SizedAtomKind, Ui, Widget,
@@ -29,6 +28,7 @@ use std::sync::Arc;
///
/// You can use this to first allocate a response and then modify, e.g., the [`Frame`] on the
/// [`AllocatedAtomLayout`] for interaction styling.
#[derive(Clone)]
pub struct AtomLayout<'a> {
id: Option<Id>,
pub atoms: Atoms<'a>,
@@ -248,7 +248,7 @@ impl<'a> AtomLayout<'a> {
let mut height: f32 = 0.0;
let mut sized_items = SmallVec::new();
let mut sized_items = Vec::new();
let mut grow_count = 0;
@@ -361,7 +361,7 @@ impl<'a> AtomLayout<'a> {
/// [`Self::paint_at`]. This is what lets one [`AtomLayout`] be nested inside another.
#[derive(Clone, Debug)]
pub struct SizedAtomLayout<'a> {
pub sized_atoms: SmallVec<[SizedAtom<'a>; ATOMS_SMALL_VEC_SIZE]>,
pub sized_atoms: Vec<SizedAtom<'a>>,
pub frame: Frame,
pub fallback_text_color: Color32,
@@ -535,6 +535,16 @@ impl<'atom> SizedAtomLayout<'atom> {
image.paint_at(ui, rect);
}
SizedAtomKind::Empty { .. } => {}
SizedAtomKind::Layout(layout) => {
// Hand the nested layout the full (possibly grown) cell width so its own
// `grow` atoms can expand, while keeping its measured height and honoring
// this atom's vertical alignment within the row.
let layout_rect = sized
.align
.align_size_within_rect(Vec2::new(frame.width(), size.y), frame);
let layout_response = ui.interact(layout_rect, layout.id, layout.sense);
layout.paint_at(ui, layout_rect, layout_response);
}
}
}

View File

@@ -1,12 +1,7 @@
use crate::{Atom, AtomKind, Image, WidgetText};
use smallvec::SmallVec;
use std::borrow::Cow;
use std::ops::{Deref, DerefMut};
// Rarely there should be more than 2 atoms in one Widget.
// I guess it could happen in a menu button with Image and right text...
pub(crate) const ATOMS_SMALL_VEC_SIZE: usize = 2;
/// A list of [`Atom`]s.
///
/// Many widgets take an `impl` [`IntoAtoms`] parameter,
@@ -18,7 +13,7 @@ pub(crate) const ATOMS_SMALL_VEC_SIZE: usize = 2;
/// ui.button((image, "Click me!"));
/// # });
#[derive(Clone, Debug, Default)]
pub struct Atoms<'a>(SmallVec<[Atom<'a>; ATOMS_SMALL_VEC_SIZE]>);
pub struct Atoms<'a>(Vec<Atom<'a>>);
impl<'a> Atoms<'a> {
pub fn new(atoms: impl IntoAtoms<'a>) -> Self {
@@ -174,7 +169,7 @@ impl<'a> Atoms<'a> {
impl<'a> IntoIterator for Atoms<'a> {
type Item = Atom<'a>;
type IntoIter = smallvec::IntoIter<[Atom<'a>; ATOMS_SMALL_VEC_SIZE]>;
type IntoIter = std::vec::IntoIter<Atom<'a>>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()

View File

@@ -1,4 +1,4 @@
use crate::Image;
use crate::{Image, SizedAtomLayout};
use emath::Vec2;
use epaint::Galley;
use std::sync::Arc;
@@ -6,9 +6,17 @@ use std::sync::Arc;
/// A sized [`crate::AtomKind`].
#[derive(Clone, Debug)]
pub enum SizedAtomKind<'a> {
Empty { size: Option<Vec2> },
Empty {
size: Option<Vec2>,
},
Text(Arc<Galley>),
Image { image: Image<'a>, size: Vec2 },
Image {
image: Image<'a>,
size: Vec2,
},
/// A measured, nested [`crate::AtomLayout`]. See [`crate::AtomKind::Layout`].
Layout(Box<SizedAtomLayout<'a>>),
}
impl Default for SizedAtomKind<'_> {
@@ -24,6 +32,7 @@ impl SizedAtomKind<'_> {
SizedAtomKind::Text(galley) => galley.size(),
SizedAtomKind::Image { image: _, size } => *size,
SizedAtomKind::Empty { size } => size.unwrap_or_default(),
SizedAtomKind::Layout(layout) => layout.frame_size,
}
}
}