Skip to main content

egui/atomics/
widget_atom.rs

1use crate::{
2    ContainerAtom, Id, IdSalt, IntoAtoms, Response, Sense, SizedContainerAtom, Ui, Widget,
3};
4use core::ops::{Deref, DerefMut};
5use emath::{Rect, Vec2};
6use smallvec::SmallVec;
7
8/// An atom-based widget: a [`ContainerAtom`] plus everything needed to show it inside a [`Ui`].
9///
10/// The [`ContainerAtom`] defines how the [`crate::Atom`]s are laid out and painted (frame, sizes,
11/// gap, alignment). The `WidgetAtom` wraps it and adds the [`Id`] and [`Sense`] used to allocate
12/// a [`Response`] and interact. This is used internally by widgets like [`crate::Button`] and
13/// [`crate::Checkbox`], and you can use it to make your own widgets.
14///
15/// Painting can be split in two phases:
16/// - [`WidgetAtom::allocate`]
17///   - measures the [`ContainerAtom`] (see [`ContainerAtom::measure`])
18///   - allocates a [`Response`]
19///   - returns an [`AllocatedWidgetAtom`]
20/// - [`AllocatedWidgetAtom::paint`]
21///   - paints the [`crate::Frame`] and each single atom
22///
23/// You can use this to first allocate a response and then modify, e.g., the [`crate::Frame`] on the
24/// [`AllocatedWidgetAtom`] for interaction styling.
25#[derive(Clone)]
26pub struct WidgetAtom<'a> {
27    pub(crate) id: Option<Id>,
28    pub(crate) sense: Sense,
29    selectable: bool,
30    pub container: ContainerAtom<'a>,
31}
32
33impl Default for WidgetAtom<'_> {
34    fn default() -> Self {
35        Self::new(())
36    }
37}
38
39impl<'a> WidgetAtom<'a> {
40    pub fn new(atoms: impl IntoAtoms<'a>) -> Self {
41        Self {
42            id: None,
43            sense: Sense::hover(),
44            selectable: false,
45            container: ContainerAtom::new(atoms),
46        }
47    }
48
49    /// Set the [`Id`] used to allocate a [`Response`].
50    #[inline]
51    pub fn id(mut self, id: Id) -> Self {
52        self.id = Some(id);
53        self
54    }
55
56    /// Set the [`Sense`] used when allocating the [`Response`].
57    #[inline]
58    pub fn sense(mut self, sense: Sense) -> Self {
59        self.sense = sense;
60        self
61    }
62
63    /// Make the text selectable with the mouse.
64    #[inline]
65    pub fn selectable(mut self, selectable: bool) -> Self {
66        self.selectable = selectable;
67        self
68    }
69
70    // The builders shared with `ContainerAtom` (`gap`, `frame`, `fallback_font`, `min_size`,
71    // `wrap_mode`, …) are generated by the `shared_container_builders!` macro in
72    // `container_atom.rs`, so the two types can never drift apart.
73
74    /// [`WidgetAtom::allocate`] and [`AllocatedWidgetAtom::paint`] in one go.
75    pub fn show(self, ui: &mut Ui) -> WidgetAtomResponse {
76        self.allocate(ui).paint(ui)
77    }
78
79    /// Measure the atoms (sizing only), without allocating space or interacting.
80    ///
81    /// This resolves the [`Id`] and measures the [`ContainerAtom`] (see
82    /// [`ContainerAtom::measure`]), but unlike [`Self::allocate`] it does *not* call
83    /// [`Ui::allocate_space`] nor [`Ui::interact`]. Use the returned [`SizedWidgetAtom`] to
84    /// allocate later via [`SizedWidgetAtom::allocate`], or to paint at an arbitrary [`Rect`] via
85    /// [`SizedWidgetAtom::paint_at`].
86    ///
87    /// `available_size` is the space available to the whole widget (frame included); it is
88    /// clamped by `max_size`/`min_size`, exactly like [`Self::allocate`] does with
89    /// [`Ui::available_size`].
90    pub fn measure(self, ui: &Ui, available_size: Vec2) -> SizedWidgetAtom<'a> {
91        let Self {
92            id,
93            mut sense,
94            selectable,
95            container,
96        } = self;
97        if selectable {
98            let allow_drag_to_select = ui.input(|i| !i.has_touch_screen());
99            let mut select_sense = if allow_drag_to_select {
100                Sense::click_and_drag()
101            } else {
102                Sense::click()
103            };
104            select_sense -= Sense::FOCUSABLE;
105            sense |= select_sense;
106        }
107        let id = id.unwrap_or_else(|| ui.next_auto_id());
108        let container = container.measure(ui, available_size);
109        SizedWidgetAtom {
110            id,
111            sense,
112            selectable,
113            container,
114        }
115    }
116
117    /// Calculate sizes, create [`crate::Galley`]s and allocate a [`Response`].
118    ///
119    /// Use the returned [`AllocatedWidgetAtom`] for painting.
120    pub fn allocate(self, ui: &mut Ui) -> AllocatedWidgetAtom<'a> {
121        self.measure(ui, ui.available_size()).allocate(ui)
122    }
123}
124
125/// A measured [`WidgetAtom`]: a [`SizedContainerAtom`] plus the [`Id`] and [`Sense`] needed to
126/// allocate a [`Response`].
127///
128/// Produced by [`WidgetAtom::measure`]. Unlike [`AllocatedWidgetAtom`], it has not yet allocated
129/// space or interacted. Call [`Self::allocate`] to do so, or [`Self::paint_at`] to interact and
130/// paint at an arbitrary [`Rect`] (used when nesting one atom-based widget inside another).
131#[derive(Clone, Debug)]
132pub struct SizedWidgetAtom<'a> {
133    /// The [`Id`] used to [`Ui::interact`] when this widget is allocated / painted.
134    id: Id,
135
136    /// The [`Sense`] used to [`Ui::interact`] when this widget is allocated / painted.
137    sense: Sense,
138
139    selectable: bool,
140
141    /// The measured container.
142    pub container: SizedContainerAtom<'a>,
143}
144
145impl<'a> SizedWidgetAtom<'a> {
146    /// Allocate space and interact, producing an [`AllocatedWidgetAtom`] ready for painting.
147    pub fn allocate(self, ui: &mut Ui) -> AllocatedWidgetAtom<'a> {
148        let (_, rect) = ui.allocate_space(self.container.outer_size);
149        let mut response = ui.interact(rect, self.id, self.sense);
150        response.set_intrinsic_size(self.container.intrinsic_size);
151
152        AllocatedWidgetAtom {
153            container: self.container,
154            selectable: self.selectable,
155            response,
156        }
157    }
158
159    /// Interact at `rect` and paint the [`crate::Frame`] and atoms there.
160    ///
161    /// Unlike [`Self::allocate`] this does not call [`Ui::allocate_space`]; it interacts at the
162    /// given `rect` using this widget's [`Id`] and [`Sense`]. This is used when nesting one
163    /// atom-based widget inside another.
164    pub fn paint_at(self, ui: &Ui, rect: Rect) -> WidgetAtomResponse {
165        let response = ui.interact(rect, self.id, self.sense);
166        let custom_rects =
167            self.container
168                .paint_at_with_selection(ui, rect, self.selectable.then_some(&response));
169        WidgetAtomResponse {
170            response,
171            custom_rects,
172        }
173    }
174}
175
176/// Instructions for painting a [`WidgetAtom`].
177///
178/// This is a [`SizedContainerAtom`] that has additionally allocated space and interacted,
179/// producing a [`Response`].
180#[derive(Clone, Debug)]
181pub struct AllocatedWidgetAtom<'a> {
182    selectable: bool,
183
184    /// The measured container.
185    pub container: SizedContainerAtom<'a>,
186
187    pub response: Response,
188}
189
190impl AllocatedWidgetAtom<'_> {
191    /// Paint the [`crate::Frame`] and individual [`crate::Atom`]s at the allocated [`Response`]'s rect.
192    pub fn paint(self, ui: &Ui) -> WidgetAtomResponse {
193        let rect = self.response.rect;
194        let custom_rects = self.container.paint_at_with_selection(
195            ui,
196            rect,
197            self.selectable.then_some(&self.response),
198        );
199        WidgetAtomResponse {
200            response: self.response,
201            custom_rects,
202        }
203    }
204}
205
206/// Response from a [`WidgetAtom::show`] or [`AllocatedWidgetAtom::paint`].
207///
208/// Use [`WidgetAtomResponse::rect`] to get the response rects from [`crate::Atom::custom`].
209#[derive(Clone, Debug)]
210pub struct WidgetAtomResponse {
211    pub response: Response,
212    // There should rarely be more than one custom rect.
213    pub(crate) custom_rects: SmallVec<[(IdSalt, Rect); 1]>,
214}
215
216impl WidgetAtomResponse {
217    pub fn empty(response: Response) -> Self {
218        Self {
219            response,
220            custom_rects: Default::default(),
221        }
222    }
223
224    pub fn custom_rects(&self) -> impl Iterator<Item = (IdSalt, Rect)> + '_ {
225        self.custom_rects.iter().copied()
226    }
227
228    /// Use this together with [`crate::Atom::custom`] to add custom painting / child widgets.
229    ///
230    /// NOTE: Don't `unwrap` rects, they might be empty when the widget is not visible.
231    pub fn rect(&self, id: IdSalt) -> Option<Rect> {
232        self.custom_rects
233            .iter()
234            .find_map(|(i, r)| if *i == id { Some(*r) } else { None })
235    }
236}
237
238impl Deref for WidgetAtomResponse {
239    type Target = Response;
240
241    fn deref(&self) -> &Self::Target {
242        &self.response
243    }
244}
245
246impl DerefMut for WidgetAtomResponse {
247    fn deref_mut(&mut self) -> &mut Self::Target {
248        &mut self.response
249    }
250}
251
252impl Widget for WidgetAtom<'_> {
253    fn ui(self, ui: &mut Ui) -> Response {
254        self.show(ui).response
255    }
256}
257
258impl<'a> Deref for WidgetAtom<'a> {
259    type Target = ContainerAtom<'a>;
260
261    fn deref(&self) -> &Self::Target {
262        &self.container
263    }
264}
265
266impl DerefMut for WidgetAtom<'_> {
267    fn deref_mut(&mut self) -> &mut Self::Target {
268        &mut self.container
269    }
270}
271
272impl<'a> Deref for SizedWidgetAtom<'a> {
273    type Target = SizedContainerAtom<'a>;
274
275    fn deref(&self) -> &Self::Target {
276        &self.container
277    }
278}
279
280impl DerefMut for SizedWidgetAtom<'_> {
281    fn deref_mut(&mut self) -> &mut Self::Target {
282        &mut self.container
283    }
284}
285
286impl<'a> Deref for AllocatedWidgetAtom<'a> {
287    type Target = SizedContainerAtom<'a>;
288
289    fn deref(&self) -> &Self::Target {
290        &self.container
291    }
292}
293
294impl DerefMut for AllocatedWidgetAtom<'_> {
295    fn deref_mut(&mut self) -> &mut Self::Target {
296        &mut self.container
297    }
298}
299
300/// `AtomLayout` was split into [`WidgetAtom`] (id, sense, allocation) and [`ContainerAtom`]
301/// (layout & painting). [`WidgetAtom`] is the direct replacement.
302#[deprecated = "Renamed to `WidgetAtom`"]
303pub type AtomLayout<'a> = WidgetAtom<'a>;
304
305/// `SizedAtomLayout` was split into [`SizedWidgetAtom`] (id, sense) and [`SizedContainerAtom`]
306/// (the measured contents). [`SizedWidgetAtom`] is the direct replacement.
307#[deprecated = "Renamed to `SizedWidgetAtom`"]
308pub type SizedAtomLayout<'a> = SizedWidgetAtom<'a>;
309
310/// Renamed to [`AllocatedWidgetAtom`].
311#[deprecated = "Renamed to `AllocatedWidgetAtom`"]
312pub type AllocatedAtomLayout<'a> = AllocatedWidgetAtom<'a>;
313
314/// Renamed to [`WidgetAtomResponse`].
315#[deprecated = "Renamed to `WidgetAtomResponse`"]
316pub type AtomLayoutResponse = WidgetAtomResponse;