From 24ee6fe756399b185f1a54c697760ba54b75c2df Mon Sep 17 00:00:00 2001 From: lucasmerlin Date: Wed, 3 Jun 2026 13:12:25 +0200 Subject: [PATCH] Deduplicate shared builder fns --- crates/egui/src/atomics/container_atom.rs | 206 ++++++++++++---------- crates/egui/src/atomics/widget_atom.rs | 109 +----------- 2 files changed, 121 insertions(+), 194 deletions(-) diff --git a/crates/egui/src/atomics/container_atom.rs b/crates/egui/src/atomics/container_atom.rs index 106f86e1a..2480cc5af 100644 --- a/crates/egui/src/atomics/container_atom.rs +++ b/crates/egui/src/atomics/container_atom.rs @@ -1,5 +1,6 @@ use crate::{ AtomKind, Atoms, FontSelection, Frame, Id, Image, IntoAtoms, SizedAtom, SizedAtomKind, Ui, + WidgetAtom, }; use emath::{Align2, GuiRounding as _, NumExt as _, Rect, Vec2}; use epaint::text::TextWrapMode; @@ -64,99 +65,6 @@ impl<'a> ContainerAtom<'a> { } } - /// Set the gap between atoms. - /// - /// Default: `Spacing::icon_spacing` - #[inline] - pub fn gap(mut self, gap: f32) -> Self { - self.gap = Some(gap); - self - } - - /// Set the [`Frame`]. - #[inline] - pub fn frame(mut self, frame: Frame) -> Self { - self.frame = frame; - self - } - - /// Set the fallback (default) text color. - /// - /// Default: [`crate::Visuals::text_color`] - #[inline] - pub fn fallback_text_color(mut self, color: Color32) -> Self { - self.fallback_text_color = Some(color); - self - } - - /// Set the fallback (default) font. - #[inline] - pub fn fallback_font(mut self, font: impl Into) -> Self { - self.fallback_font = Some(font.into()); - self - } - - /// Set the minimum size of the Widget. - /// - /// This will find and expand atoms with `grow: true`. - /// If there are no growable atoms then everything will be left-aligned. - #[inline] - pub fn min_size(mut self, size: Vec2) -> Self { - self.min_size = size; - self - } - - /// Set the maximum size of the Widget. - /// - /// By default, the size is limited by the available size in the [`Ui`]. - #[inline] - pub fn max_size(mut self, size: Vec2) -> Self { - self.max_size = size; - self - } - - /// Set the maximum width of the Widget. - /// - /// By default, the width is limited by the available width in the [`Ui`]. - #[inline] - pub fn max_width(mut self, width: f32) -> Self { - self.max_size.x = width; - self - } - - /// Set the maximum height of the Widget. - /// - /// By default, the height is limited by the available height in the [`Ui`]. - #[inline] - pub fn max_height(mut self, height: f32) -> Self { - self.max_size.y = height; - self - } - - /// Set the [`TextWrapMode`] for the [`crate::Atom`] marked as `shrink`. - /// - /// Only a single [`crate::Atom`] may shrink. If this (or `ui.wrap_mode()`) is not - /// [`TextWrapMode::Extend`] and no item is set to shrink, the first (left-most) - /// [`AtomKind::Text`] will be set to shrink. - #[inline] - pub fn wrap_mode(mut self, wrap_mode: TextWrapMode) -> Self { - self.wrap_mode = Some(wrap_mode); - self - } - - /// Set the [`Align2`]. - /// - /// This will align the [`crate::Atom`]s within the [`Rect`] returned by [`Ui::allocate_space`]. - /// - /// The default is chosen based on the [`Ui`]s [`crate::Layout`]. See - /// [this snapshot](https://github.com/emilk/egui/blob/master/tests/egui_tests/tests/snapshots/layout/button.png) - /// for info on how the [`crate::Layout`] affects the alignment. - #[inline] - pub fn align2(mut self, align2: Align2) -> Self { - self.align2 = Some(align2); - self - } - /// Measure the atoms (sizing only), without allocating space or interacting. /// /// This converts texts to [`Galley`]s and calculates sizes, but it does *not* call @@ -312,6 +220,118 @@ impl<'a> ContainerAtom<'a> { } } +/// Generates the layout-builder methods shared by [`ContainerAtom`] and [`WidgetAtom`] from a +/// single definition, so the two can never drift apart. +/// +/// Each entry is written as it appears on [`ContainerAtom`] (mutating its own fields). The +/// matching method on [`WidgetAtom`] is generated automatically, forwarding to its inner +/// `container`. [`WidgetAtom`]-only builders (`id`, `sense`) stay inherent on [`WidgetAtom`]. +macro_rules! shared_container_builders { + ( + $( + $(#[$meta:meta])* + fn $name:ident($self:ident, $($arg:ident: $arg_ty:ty),* $(,)?) $body:block + )* + ) => { + impl<'a> ContainerAtom<'a> { + $( + $(#[$meta])* + #[inline] + pub fn $name(mut $self, $($arg: $arg_ty),*) -> Self { + $body + $self + } + )* + } + + impl<'a> WidgetAtom<'a> { + $( + $(#[$meta])* + #[inline] + pub fn $name(mut self, $($arg: $arg_ty),*) -> Self { + self.container = self.container.$name($($arg),*); + self + } + )* + } + }; +} + +shared_container_builders! { + /// Set the gap between atoms. + /// + /// Default: `Spacing::icon_spacing` + fn gap(self, gap: f32) { + self.gap = Some(gap); + } + + /// Set the [`Frame`]. + fn frame(self, frame: Frame) { + self.frame = frame; + } + + /// Set the fallback (default) text color. + /// + /// Default: [`crate::Visuals::text_color`] + fn fallback_text_color(self, color: Color32) { + self.fallback_text_color = Some(color); + } + + /// Set the fallback (default) font. + fn fallback_font(self, font: impl Into) { + self.fallback_font = Some(font.into()); + } + + /// Set the minimum size of the Widget. + /// + /// This will find and expand atoms with `grow: true`. + /// If there are no growable atoms then everything will be left-aligned. + fn min_size(self, size: Vec2) { + self.min_size = size; + } + + /// Set the maximum size of the Widget. + /// + /// By default, the size is limited by the available size in the [`Ui`]. + fn max_size(self, size: Vec2) { + self.max_size = size; + } + + /// Set the maximum width of the Widget. + /// + /// By default, the width is limited by the available width in the [`Ui`]. + fn max_width(self, width: f32) { + self.max_size.x = width; + } + + /// Set the maximum height of the Widget. + /// + /// By default, the height is limited by the available height in the [`Ui`]. + fn max_height(self, height: f32) { + self.max_size.y = height; + } + + /// Set the [`TextWrapMode`] for the [`crate::Atom`] marked as `shrink`. + /// + /// Only a single [`crate::Atom`] may shrink. If this (or `ui.wrap_mode()`) is not + /// [`TextWrapMode::Extend`] and no item is set to shrink, the first (left-most) + /// [`AtomKind::Text`] will be set to shrink. + fn wrap_mode(self, wrap_mode: TextWrapMode) { + self.wrap_mode = Some(wrap_mode); + } + + /// Set the [`Align2`]. + /// + /// This will align the [`crate::Atom`]s within the [`Rect`] returned by [`Ui::allocate_space`]. + /// + /// The default is chosen based on the [`Ui`]s [`crate::Layout`]. See + /// [this snapshot](https://github.com/emilk/egui/blob/master/tests/egui_tests/tests/snapshots/layout/button.png) + /// for info on how the [`crate::Layout`] affects the alignment. + fn align2(self, align2: Align2) { + self.align2 = Some(align2); + } +} + /// A measured [`ContainerAtom`], ready to be painted at a [`Rect`]. /// /// Produced by [`ContainerAtom::measure`]. It has not yet allocated space or interacted, so it diff --git a/crates/egui/src/atomics/widget_atom.rs b/crates/egui/src/atomics/widget_atom.rs index 00d9bd5ad..587846e9e 100644 --- a/crates/egui/src/atomics/widget_atom.rs +++ b/crates/egui/src/atomics/widget_atom.rs @@ -1,9 +1,5 @@ -use crate::{ - Align2, Color32, ContainerAtom, FontSelection, Frame, Id, IntoAtoms, Response, Sense, - SizedContainerAtom, Ui, Widget, -}; +use crate::{ContainerAtom, Id, IntoAtoms, Response, Sense, SizedContainerAtom, Ui, Widget}; use emath::{Rect, Vec2}; -use epaint::text::TextWrapMode; use smallvec::SmallVec; use std::ops::{Deref, DerefMut}; @@ -20,9 +16,9 @@ use std::ops::{Deref, DerefMut}; /// - allocates a [`Response`] /// - returns an [`AllocatedWidgetAtom`] /// - [`AllocatedWidgetAtom::paint`] -/// - paints the [`Frame`] and each single atom +/// - paints the [`crate::Frame`] and each single atom /// -/// 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 [`crate::Frame`] on the /// [`AllocatedWidgetAtom`] for interaction styling. #[derive(Clone)] pub struct WidgetAtom<'a> { @@ -60,98 +56,9 @@ impl<'a> WidgetAtom<'a> { self } - /// Set the gap between atoms. - /// - /// Default: `Spacing::icon_spacing` - #[inline] - pub fn gap(mut self, gap: f32) -> Self { - self.container = self.container.gap(gap); - self - } - - /// Set the [`Frame`]. - #[inline] - pub fn frame(mut self, frame: Frame) -> Self { - self.container = self.container.frame(frame); - self - } - - /// Set the fallback (default) text color. - /// - /// Default: [`crate::Visuals::text_color`] - #[inline] - pub fn fallback_text_color(mut self, color: Color32) -> Self { - self.container = self.container.fallback_text_color(color); - self - } - - /// Set the fallback (default) font. - #[inline] - pub fn fallback_font(mut self, font: impl Into) -> Self { - self.container = self.container.fallback_font(font); - self - } - - /// Set the minimum size of the Widget. - /// - /// This will find and expand atoms with `grow: true`. - /// If there are no growable atoms then everything will be left-aligned. - #[inline] - pub fn min_size(mut self, size: Vec2) -> Self { - self.container = self.container.min_size(size); - self - } - - /// Set the maximum size of the Widget. - /// - /// By default, the size is limited by the available size in the [`Ui`]. - #[inline] - pub fn max_size(mut self, size: Vec2) -> Self { - self.container = self.container.max_size(size); - self - } - - /// Set the maximum width of the Widget. - /// - /// By default, the width is limited by the available width in the [`Ui`]. - #[inline] - pub fn max_width(mut self, width: f32) -> Self { - self.container = self.container.max_width(width); - self - } - - /// Set the maximum height of the Widget. - /// - /// By default, the height is limited by the available height in the [`Ui`]. - #[inline] - pub fn max_height(mut self, height: f32) -> Self { - self.container = self.container.max_height(height); - self - } - - /// Set the [`TextWrapMode`] for the [`crate::Atom`] marked as `shrink`. - /// - /// Only a single [`crate::Atom`] may shrink. If this (or `ui.wrap_mode()`) is not - /// [`TextWrapMode::Extend`] and no item is set to shrink, the first (left-most) - /// [`crate::AtomKind::Text`] will be set to shrink. - #[inline] - pub fn wrap_mode(mut self, wrap_mode: TextWrapMode) -> Self { - self.container = self.container.wrap_mode(wrap_mode); - self - } - - /// Set the [`Align2`]. - /// - /// This will align the [`crate::Atom`]s within the [`Rect`] returned by [`Ui::allocate_space`]. - /// - /// The default is chosen based on the [`Ui`]s [`crate::Layout`]. See - /// [this snapshot](https://github.com/emilk/egui/blob/master/tests/egui_tests/tests/snapshots/layout/button.png) - /// for info on how the [`crate::Layout`] affects the alignment. - #[inline] - pub fn align2(mut self, align2: Align2) -> Self { - self.container = self.container.align2(align2); - self - } + // The builders shared with `ContainerAtom` (`gap`, `frame`, `fallback_font`, `min_size`, + // `wrap_mode`, …) are generated by the `shared_container_builders!` macro in + // `container_atom.rs`, so the two types can never drift apart. /// [`WidgetAtom::allocate`] and [`AllocatedWidgetAtom::paint`] in one go. pub fn show(self, ui: &mut Ui) -> WidgetAtomResponse { @@ -223,7 +130,7 @@ impl<'a> SizedWidgetAtom<'a> { } } - /// Interact at `rect` and paint the [`Frame`] and atoms there. + /// Interact at `rect` and paint the [`crate::Frame`] and atoms there. /// /// Unlike [`Self::allocate`] this does not call [`Ui::allocate_space`]; it interacts at the /// given `rect` using this widget's [`Id`] and [`Sense`]. This is used when nesting one @@ -251,7 +158,7 @@ pub struct AllocatedWidgetAtom<'a> { } impl AllocatedWidgetAtom<'_> { - /// Paint the [`Frame`] and individual [`crate::Atom`]s at the allocated [`Response`]'s rect. + /// Paint the [`crate::Frame`] and individual [`crate::Atom`]s at the allocated [`Response`]'s rect. pub fn paint(self, ui: &Ui) -> WidgetAtomResponse { let rect = self.response.rect; let custom_rects = self.container.paint_at(ui, rect);