1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-31 05:40:03 -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 emath::{Align2, NumExt as _, Vec2};
use epaint::text::TextWrapMode; 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`]. /// Turn this into a [`SizedAtom`].
pub fn into_sized( pub fn into_sized(
self, 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 emath::Vec2;
use epaint::text::TextWrapMode; use epaint::text::TextWrapMode;
use std::fmt::Debug; use std::fmt::Debug;
@@ -65,6 +65,13 @@ pub enum AtomKind<'a> {
/// Note: This api is experimental, expect breaking changes here. /// Note: This api is experimental, expect breaking changes here.
/// When cloning, this will be cloned as [`AtomKind::Empty`]. /// When cloning, this will be cloned as [`AtomKind::Empty`].
Closure(AtomClosure<'a>), 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<'_> { impl Clone for AtomKind<'_> {
@@ -77,6 +84,7 @@ impl Clone for AtomKind<'_> {
log::warn!("Cannot clone atom closures"); log::warn!("Cannot clone atom closures");
AtomKind::Empty 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::Text(text) => write!(f, "AtomKind::Text({text:?})"),
AtomKind::Image(image) => write!(f, "AtomKind::Image({image:?})"), AtomKind::Image(image) => write!(f, "AtomKind::Image({image:?})"),
AtomKind::Closure(_) => write!(f, "AtomKind::Closure(<closure>)"), AtomKind::Closure(_) => write!(f, "AtomKind::Closure(<closure>)"),
AtomKind::Layout(_) => write!(f, "AtomKind::Layout(<layout>)"),
} }
} }
} }
@@ -149,6 +158,15 @@ impl<'a> AtomKind<'a> {
fallback_font, 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::{ use crate::{
AtomKind, Atoms, FontSelection, Frame, Id, Image, IntoAtoms, Response, Sense, SizedAtom, AtomKind, Atoms, FontSelection, Frame, Id, Image, IntoAtoms, Response, Sense, SizedAtom,
SizedAtomKind, Ui, Widget, 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 /// You can use this to first allocate a response and then modify, e.g., the [`Frame`] on the
/// [`AllocatedAtomLayout`] for interaction styling. /// [`AllocatedAtomLayout`] for interaction styling.
#[derive(Clone)]
pub struct AtomLayout<'a> { pub struct AtomLayout<'a> {
id: Option<Id>, id: Option<Id>,
pub atoms: Atoms<'a>, pub atoms: Atoms<'a>,
@@ -248,7 +248,7 @@ impl<'a> AtomLayout<'a> {
let mut height: f32 = 0.0; let mut height: f32 = 0.0;
let mut sized_items = SmallVec::new(); let mut sized_items = Vec::new();
let mut grow_count = 0; 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. /// [`Self::paint_at`]. This is what lets one [`AtomLayout`] be nested inside another.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct SizedAtomLayout<'a> { pub struct SizedAtomLayout<'a> {
pub sized_atoms: SmallVec<[SizedAtom<'a>; ATOMS_SMALL_VEC_SIZE]>, pub sized_atoms: Vec<SizedAtom<'a>>,
pub frame: Frame, pub frame: Frame,
pub fallback_text_color: Color32, pub fallback_text_color: Color32,
@@ -535,6 +535,16 @@ impl<'atom> SizedAtomLayout<'atom> {
image.paint_at(ui, rect); image.paint_at(ui, rect);
} }
SizedAtomKind::Empty { .. } => {} 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 crate::{Atom, AtomKind, Image, WidgetText};
use smallvec::SmallVec;
use std::borrow::Cow; use std::borrow::Cow;
use std::ops::{Deref, DerefMut}; 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. /// A list of [`Atom`]s.
/// ///
/// Many widgets take an `impl` [`IntoAtoms`] parameter, /// 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!")); /// ui.button((image, "Click me!"));
/// # }); /// # });
#[derive(Clone, Debug, Default)] #[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> { impl<'a> Atoms<'a> {
pub fn new(atoms: impl IntoAtoms<'a>) -> Self { pub fn new(atoms: impl IntoAtoms<'a>) -> Self {
@@ -174,7 +169,7 @@ impl<'a> Atoms<'a> {
impl<'a> IntoIterator for Atoms<'a> { impl<'a> IntoIterator for Atoms<'a> {
type Item = Atom<'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 { fn into_iter(self) -> Self::IntoIter {
self.0.into_iter() self.0.into_iter()

View File

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