diff --git a/crates/egui/src/atomics/atom.rs b/crates/egui/src/atomics/atom.rs index 6f289fcfb..8a980c7b6 100644 --- a/crates/egui/src/atomics/atom.rs +++ b/crates/egui/src/atomics/atom.rs @@ -46,6 +46,9 @@ pub struct Atom<'a> { /// See [`crate::AtomExt::atom_align`] pub align: Align2, + /// See [`crate::AtomExt::atom_ignore_spacing`] + pub ignore_spacing: bool, + /// The atom type / content pub kind: AtomKind<'a>, } @@ -58,6 +61,7 @@ impl Default for Atom<'_> { max_size: Vec2::INFINITY, grow: false, shrink: false, + ignore_spacing: false, align: Align2::CENTER_CENTER, kind: AtomKind::Empty, } @@ -72,6 +76,7 @@ impl<'a> Atom<'a> { pub fn grow() -> Self { Atom { grow: true, + ignore_spacing: true, ..Default::default() } } @@ -144,6 +149,7 @@ impl<'a> Atom<'a> { size, intrinsic_size: intrinsic_size.at_least(self.size.unwrap_or_default()), grow: self.grow, + ignore_spacing: self.ignore_spacing, align: self.align, kind: sized, } diff --git a/crates/egui/src/atomics/atom_ext.rs b/crates/egui/src/atomics/atom_ext.rs index bfe587fae..d17d15cb7 100644 --- a/crates/egui/src/atomics/atom_ext.rs +++ b/crates/egui/src/atomics/atom_ext.rs @@ -70,6 +70,12 @@ pub trait AtomExt<'a> { self.atom_max_height(height) } + /// If `true`, this atom will not contribute to inter-atom gap spacing. + /// + /// This is useful for invisible spacers like [`Atom::grow()`] where you want + /// the grow space to replace the gaps rather than adding to them. + fn atom_ignore_spacing(self, ignore_spacing: bool) -> Atom<'a>; + /// Sets the [`emath::Align2`] of a single atom within its available space. /// /// Defaults to center-center. @@ -122,6 +128,12 @@ where atom } + fn atom_ignore_spacing(self, ignore_spacing: bool) -> Atom<'a> { + let mut atom = self.into(); + atom.ignore_spacing = ignore_spacing; + atom + } + fn atom_align(self, align: emath::Align2) -> Atom<'a> { let mut atom = self.into(); atom.align = align; diff --git a/crates/egui/src/atomics/atom_layout.rs b/crates/egui/src/atomics/atom_layout.rs index 7894273f3..607d050fe 100644 --- a/crates/egui/src/atomics/atom_layout.rs +++ b/crates/egui/src/atomics/atom_layout.rs @@ -10,6 +10,33 @@ use smallvec::SmallVec; use std::ops::{Deref, DerefMut}; use std::sync::Arc; +/// Compute the effective gap between atom `i` and atom `i+1`. +/// +/// Each atom with `ignore_spacing` has a "budget" of 1 full gap to remove, +/// distributed across its adjacent gaps: +/// - Edge atom (first or last): 1 adjacent gap → fully removed +/// - Middle atom: 2 adjacent gaps → each reduced by 0.5× +fn gap_between( + ignore_spacing: impl Fn(usize) -> bool, + count: usize, + gap: f32, + i: usize, +) -> f32 { + debug_assert!(i + 1 < count); + let mut reduction = 0.0_f32; + if ignore_spacing(i) { + reduction += if i == 0 || i == count - 1 { 1.0 } else { 0.5 }; + } + if ignore_spacing(i + 1) { + reduction += if i + 1 == 0 || i + 1 == count - 1 { + 1.0 + } else { + 0.5 + }; + } + gap * (1.0 - reduction).max(0.0) +} + /// Intra-widget layout utility. /// /// Used to lay out and paint [`crate::Atom`]s. @@ -250,8 +277,22 @@ impl<'a> AtomLayout<'a> { Align2([ui.layout().horizontal_align(), ui.layout().vertical_align()]) }); + // Collect ignore_spacing flags before atoms are consumed by into_sized. + let ignore_spacing_flags: SmallVec<[bool; ATOMS_SMALL_VEC_SIZE]> = + atoms.iter().map(|a| a.ignore_spacing).collect(); + if atoms.len() > 1 { - let gap_space = gap * (atoms.len() as f32 - 1.0); + let atom_count = atoms.len(); + let gap_space: f32 = (0..atom_count - 1) + .map(|i| { + gap_between( + |idx| ignore_spacing_flags[idx], + atom_count, + gap, + i, + ) + }) + .sum(); desired_width += gap_space; intrinsic_width += gap_space; } @@ -454,7 +495,10 @@ impl<'atom> AllocatedAtomLayout<'atom> { let mut response = AtomLayoutResponse::empty(response); - for sized in sized_atoms { + let atom_count = sized_atoms.len(); + let ignore_flags: SmallVec<[bool; ATOMS_SMALL_VEC_SIZE]> = + sized_atoms.iter().map(|a| a.ignore_spacing()).collect(); + for (i, sized) in sized_atoms.into_iter().enumerate() { let size = sized.size; // TODO(lucasmerlin): This is not ideal, since this might lead to accumulated rounding errors // https://github.com/emilk/egui/pull/5830#discussion_r2079627864 @@ -463,7 +507,12 @@ impl<'atom> AllocatedAtomLayout<'atom> { let frame = aligned_rect .with_min_x(cursor) .with_max_x(cursor + size.x + growth); - cursor = frame.right() + gap; + let effective_gap = if i + 1 < atom_count { + gap_between(|idx| ignore_flags[idx], atom_count, gap, i) + } else { + 0.0 + }; + cursor = frame.right() + effective_gap; let rect = sized.align.align_size_within_rect(size, frame); if let Some(id) = sized.id { diff --git a/crates/egui/src/atomics/sized_atom.rs b/crates/egui/src/atomics/sized_atom.rs index 19c289ab3..20ef65c2c 100644 --- a/crates/egui/src/atomics/sized_atom.rs +++ b/crates/egui/src/atomics/sized_atom.rs @@ -8,6 +8,8 @@ pub struct SizedAtom<'a> { pub(crate) grow: bool, + pub(crate) ignore_spacing: bool, + /// The size of the atom. /// /// Used for placing this atom in [`crate::AtomLayout`], the cursor will advance by @@ -28,4 +30,9 @@ impl SizedAtom<'_> { pub fn is_grow(&self) -> bool { self.grow } + + /// Was this [`crate::Atom`] marked as `ignore_spacing`? + pub fn ignore_spacing(&self) -> bool { + self.ignore_spacing + } } diff --git a/tests/egui_tests/tests/snapshots/button_shortcut.png b/tests/egui_tests/tests/snapshots/button_shortcut.png index de7d64b4d..1361b77fb 100644 --- a/tests/egui_tests/tests/snapshots/button_shortcut.png +++ b/tests/egui_tests/tests/snapshots/button_shortcut.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cbf68b6934dae0868bc9cf0891baf5acf110284d297cfa348e756237fca64a28 -size 1564 +oid sha256:b0b36160eff3335d10951d83cb20053bb15ff4b6fab68580710b509cd112b668 +size 1563 diff --git a/tests/egui_tests/tests/snapshots/layout/button_image_shortcut.png b/tests/egui_tests/tests/snapshots/layout/button_image_shortcut.png index 9c74cd8be..0ceb1125f 100644 --- a/tests/egui_tests/tests/snapshots/layout/button_image_shortcut.png +++ b/tests/egui_tests/tests/snapshots/layout/button_image_shortcut.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:231ceab75a602eedcd11f4f4ed34f38fb9d072f5cb54e135a7e02d33d257f86b -size 433973 +oid sha256:82f4334e848e762aa28a29904b569ca2af12b38f308434bdf5968f607fc2be0a +size 433829 diff --git a/tests/egui_tests/tests/snapshots/layout/text_edit_prefix_suffix.png b/tests/egui_tests/tests/snapshots/layout/text_edit_prefix_suffix.png index bdcab38f2..de4c748dc 100644 --- a/tests/egui_tests/tests/snapshots/layout/text_edit_prefix_suffix.png +++ b/tests/egui_tests/tests/snapshots/layout/text_edit_prefix_suffix.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cf00e99dbfdf7497688955feb8c417fab0a366588d92182eccee775abade5179 -size 361876 +oid sha256:2d018426f3014e79475d8d8f46a728632d7f15f2bfbf55b0ee648a56c792495c +size 370212 diff --git a/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut.png b/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut.png index b278f6c25..d94a282fd 100644 --- a/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut.png +++ b/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e298d89e6fb434e5010d96661fca40bf119118b6b31fdd9fc13201bcd74c8ffd -size 15149 +oid sha256:99f33211696d454e1fdbfa39e94af4c09239a38e00a3c4ef22e36adfbb2ee3c4 +size 15073 diff --git a/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut_selected.png b/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut_selected.png index 9a1e15c20..0f6214da3 100644 --- a/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut_selected.png +++ b/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut_selected.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a0581d601f1e536298cb52bfc8a167aa37aebdf065fc910973a752c9c159223d -size 14733 +oid sha256:6a4d8b64c9705e13e610a404b3eca780ecd26d7d10dba1c54207c6930018d06b +size 14659 diff --git a/tests/egui_tests/tests/snapshots/visuals/text_edit_prefix_suffix.png b/tests/egui_tests/tests/snapshots/visuals/text_edit_prefix_suffix.png index d27f6f8c4..f28a4f09a 100644 --- a/tests/egui_tests/tests/snapshots/visuals/text_edit_prefix_suffix.png +++ b/tests/egui_tests/tests/snapshots/visuals/text_edit_prefix_suffix.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:337dcbf0b3a344c6cadaf9500376a627739e19e9c47b5da23786c98c612ef4dc -size 10028 +oid sha256:d47e3584a09ea9b024dd36293acac5d97a1784eb87a469c2aec4b1db455b4e45 +size 10514