egui 0.37.0

An easy-to-use immediate mode GUI that runs on both web and native
Documentation
use crate::{
    ContainerAtom, Id, IdSalt, IntoAtoms, Response, Sense, SizedContainerAtom, Ui, Widget,
};
use core::ops::{Deref, DerefMut};
use emath::{Rect, Vec2};
use smallvec::SmallVec;

/// An atom-based widget: a [`ContainerAtom`] plus everything needed to show it inside a [`Ui`].
///
/// The [`ContainerAtom`] defines how the [`crate::Atom`]s are laid out and painted (frame, sizes,
/// gap, alignment). The `WidgetAtom` wraps it and adds the [`Id`] and [`Sense`] used to allocate
/// a [`Response`] and interact. This is used internally by widgets like [`crate::Button`] and
/// [`crate::Checkbox`], and you can use it to make your own widgets.
///
/// Painting can be split in two phases:
/// - [`WidgetAtom::allocate`]
///   - measures the [`ContainerAtom`] (see [`ContainerAtom::measure`])
///   - allocates a [`Response`]
///   - returns an [`AllocatedWidgetAtom`]
/// - [`AllocatedWidgetAtom::paint`]
///   - paints the [`crate::Frame`] and each single atom
///
/// 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> {
    pub(crate) id: Option<Id>,
    pub(crate) sense: Sense,
    selectable: bool,
    pub container: ContainerAtom<'a>,
}

impl Default for WidgetAtom<'_> {
    fn default() -> Self {
        Self::new(())
    }
}

impl<'a> WidgetAtom<'a> {
    pub fn new(atoms: impl IntoAtoms<'a>) -> Self {
        Self {
            id: None,
            sense: Sense::hover(),
            selectable: false,
            container: ContainerAtom::new(atoms),
        }
    }

    /// Set the [`Id`] used to allocate a [`Response`].
    #[inline]
    pub fn id(mut self, id: Id) -> Self {
        self.id = Some(id);
        self
    }

    /// Set the [`Sense`] used when allocating the [`Response`].
    #[inline]
    pub fn sense(mut self, sense: Sense) -> Self {
        self.sense = sense;
        self
    }

    /// Make the text selectable with the mouse.
    #[inline]
    pub fn selectable(mut self, selectable: bool) -> Self {
        self.selectable = selectable;
        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 {
        self.allocate(ui).paint(ui)
    }

    /// Measure the atoms (sizing only), without allocating space or interacting.
    ///
    /// This resolves the [`Id`] and measures the [`ContainerAtom`] (see
    /// [`ContainerAtom::measure`]), but unlike [`Self::allocate`] it does *not* call
    /// [`Ui::allocate_space`] nor [`Ui::interact`]. Use the returned [`SizedWidgetAtom`] to
    /// allocate later via [`SizedWidgetAtom::allocate`], or to paint at an arbitrary [`Rect`] via
    /// [`SizedWidgetAtom::paint_at`].
    ///
    /// `available_size` is the space available to the whole widget (frame included); it is
    /// clamped by `max_size`/`min_size`, exactly like [`Self::allocate`] does with
    /// [`Ui::available_size`].
    pub fn measure(self, ui: &Ui, available_size: Vec2) -> SizedWidgetAtom<'a> {
        let Self {
            id,
            mut sense,
            selectable,
            container,
        } = self;
        if selectable {
            let allow_drag_to_select = ui.input(|i| !i.has_touch_screen());
            let mut select_sense = if allow_drag_to_select {
                Sense::click_and_drag()
            } else {
                Sense::click()
            };
            select_sense -= Sense::FOCUSABLE;
            sense |= select_sense;
        }
        let id = id.unwrap_or_else(|| ui.next_auto_id());
        let container = container.measure(ui, available_size);
        SizedWidgetAtom {
            id,
            sense,
            selectable,
            container,
        }
    }

    /// Calculate sizes, create [`crate::Galley`]s and allocate a [`Response`].
    ///
    /// Use the returned [`AllocatedWidgetAtom`] for painting.
    pub fn allocate(self, ui: &mut Ui) -> AllocatedWidgetAtom<'a> {
        self.measure(ui, ui.available_size()).allocate(ui)
    }
}

/// A measured [`WidgetAtom`]: a [`SizedContainerAtom`] plus the [`Id`] and [`Sense`] needed to
/// allocate a [`Response`].
///
/// Produced by [`WidgetAtom::measure`]. Unlike [`AllocatedWidgetAtom`], it has not yet allocated
/// space or interacted. Call [`Self::allocate`] to do so, or [`Self::paint_at`] to interact and
/// paint at an arbitrary [`Rect`] (used when nesting one atom-based widget inside another).
#[derive(Clone, Debug)]
pub struct SizedWidgetAtom<'a> {
    /// The [`Id`] used to [`Ui::interact`] when this widget is allocated / painted.
    id: Id,

    /// The [`Sense`] used to [`Ui::interact`] when this widget is allocated / painted.
    sense: Sense,

    selectable: bool,

    /// The measured container.
    pub container: SizedContainerAtom<'a>,
}

impl<'a> SizedWidgetAtom<'a> {
    /// Allocate space and interact, producing an [`AllocatedWidgetAtom`] ready for painting.
    pub fn allocate(self, ui: &mut Ui) -> AllocatedWidgetAtom<'a> {
        let (_, rect) = ui.allocate_space(self.container.outer_size);
        let mut response = ui.interact(rect, self.id, self.sense);
        response.set_intrinsic_size(self.container.intrinsic_size);

        AllocatedWidgetAtom {
            container: self.container,
            selectable: self.selectable,
            response,
        }
    }

    /// 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
    /// atom-based widget inside another.
    pub fn paint_at(self, ui: &Ui, rect: Rect) -> WidgetAtomResponse {
        let response = ui.interact(rect, self.id, self.sense);
        let custom_rects =
            self.container
                .paint_at_with_selection(ui, rect, self.selectable.then_some(&response));
        WidgetAtomResponse {
            response,
            custom_rects,
        }
    }
}

/// Instructions for painting a [`WidgetAtom`].
///
/// This is a [`SizedContainerAtom`] that has additionally allocated space and interacted,
/// producing a [`Response`].
#[derive(Clone, Debug)]
pub struct AllocatedWidgetAtom<'a> {
    selectable: bool,

    /// The measured container.
    pub container: SizedContainerAtom<'a>,

    pub response: Response,
}

impl AllocatedWidgetAtom<'_> {
    /// 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_with_selection(
            ui,
            rect,
            self.selectable.then_some(&self.response),
        );
        WidgetAtomResponse {
            response: self.response,
            custom_rects,
        }
    }
}

/// Response from a [`WidgetAtom::show`] or [`AllocatedWidgetAtom::paint`].
///
/// Use [`WidgetAtomResponse::rect`] to get the response rects from [`crate::Atom::custom`].
#[derive(Clone, Debug)]
pub struct WidgetAtomResponse {
    pub response: Response,
    // There should rarely be more than one custom rect.
    pub(crate) custom_rects: SmallVec<[(IdSalt, Rect); 1]>,
}

impl WidgetAtomResponse {
    pub fn empty(response: Response) -> Self {
        Self {
            response,
            custom_rects: Default::default(),
        }
    }

    pub fn custom_rects(&self) -> impl Iterator<Item = (IdSalt, Rect)> + '_ {
        self.custom_rects.iter().copied()
    }

    /// Use this together with [`crate::Atom::custom`] to add custom painting / child widgets.
    ///
    /// NOTE: Don't `unwrap` rects, they might be empty when the widget is not visible.
    pub fn rect(&self, id: IdSalt) -> Option<Rect> {
        self.custom_rects
            .iter()
            .find_map(|(i, r)| if *i == id { Some(*r) } else { None })
    }
}

impl Deref for WidgetAtomResponse {
    type Target = Response;

    fn deref(&self) -> &Self::Target {
        &self.response
    }
}

impl DerefMut for WidgetAtomResponse {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.response
    }
}

impl Widget for WidgetAtom<'_> {
    fn ui(self, ui: &mut Ui) -> Response {
        self.show(ui).response
    }
}

impl<'a> Deref for WidgetAtom<'a> {
    type Target = ContainerAtom<'a>;

    fn deref(&self) -> &Self::Target {
        &self.container
    }
}

impl DerefMut for WidgetAtom<'_> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.container
    }
}

impl<'a> Deref for SizedWidgetAtom<'a> {
    type Target = SizedContainerAtom<'a>;

    fn deref(&self) -> &Self::Target {
        &self.container
    }
}

impl DerefMut for SizedWidgetAtom<'_> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.container
    }
}

impl<'a> Deref for AllocatedWidgetAtom<'a> {
    type Target = SizedContainerAtom<'a>;

    fn deref(&self) -> &Self::Target {
        &self.container
    }
}

impl DerefMut for AllocatedWidgetAtom<'_> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.container
    }
}

/// `AtomLayout` was split into [`WidgetAtom`] (id, sense, allocation) and [`ContainerAtom`]
/// (layout & painting). [`WidgetAtom`] is the direct replacement.
#[deprecated = "Renamed to `WidgetAtom`"]
pub type AtomLayout<'a> = WidgetAtom<'a>;

/// `SizedAtomLayout` was split into [`SizedWidgetAtom`] (id, sense) and [`SizedContainerAtom`]
/// (the measured contents). [`SizedWidgetAtom`] is the direct replacement.
#[deprecated = "Renamed to `SizedWidgetAtom`"]
pub type SizedAtomLayout<'a> = SizedWidgetAtom<'a>;

/// Renamed to [`AllocatedWidgetAtom`].
#[deprecated = "Renamed to `AllocatedWidgetAtom`"]
pub type AllocatedAtomLayout<'a> = AllocatedWidgetAtom<'a>;

/// Renamed to [`WidgetAtomResponse`].
#[deprecated = "Renamed to `WidgetAtomResponse`"]
pub type AtomLayoutResponse = WidgetAtomResponse;