mirror of
https://github.com/emilk/egui.git
synced 2026-09-02 23:00:04 -04:00
⚠️ Atom improvements: Atom::id, align, closure, max_size (#7958)
Migration guide: - `AtomKind::Custom` has been removed. You can now set an id to any kind via `Atom::custom` or `AtomExt::atom_id`.
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
use crate::{AtomKind, FontSelection, Id, SizedAtom, Ui};
|
use crate::{AtomKind, FontSelection, Id, IntoSizedArgs, IntoSizedResult, SizedAtom, Ui};
|
||||||
use emath::{NumExt as _, Vec2};
|
use emath::{Align2, NumExt as _, Vec2};
|
||||||
use epaint::text::TextWrapMode;
|
use epaint::text::TextWrapMode;
|
||||||
|
|
||||||
/// A low-level ui building block.
|
/// A low-level ui building block.
|
||||||
@@ -14,6 +14,9 @@ use epaint::text::TextWrapMode;
|
|||||||
/// ```
|
/// ```
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Atom<'a> {
|
pub struct Atom<'a> {
|
||||||
|
/// See [`crate::AtomExt::atom_id`]
|
||||||
|
pub id: Option<Id>,
|
||||||
|
|
||||||
/// See [`crate::AtomExt::atom_size`]
|
/// See [`crate::AtomExt::atom_size`]
|
||||||
pub size: Option<Vec2>,
|
pub size: Option<Vec2>,
|
||||||
|
|
||||||
@@ -26,17 +29,22 @@ pub struct Atom<'a> {
|
|||||||
/// See [`crate::AtomExt::atom_shrink`]
|
/// See [`crate::AtomExt::atom_shrink`]
|
||||||
pub shrink: bool,
|
pub shrink: bool,
|
||||||
|
|
||||||
/// The atom type
|
/// See [`crate::AtomExt::atom_align`]
|
||||||
|
pub align: Align2,
|
||||||
|
|
||||||
|
/// The atom type / content
|
||||||
pub kind: AtomKind<'a>,
|
pub kind: AtomKind<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Atom<'_> {
|
impl Default for Atom<'_> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Atom {
|
Atom {
|
||||||
|
id: None,
|
||||||
size: None,
|
size: None,
|
||||||
max_size: Vec2::INFINITY,
|
max_size: Vec2::INFINITY,
|
||||||
grow: false,
|
grow: false,
|
||||||
shrink: false,
|
shrink: false,
|
||||||
|
align: Align2::CENTER_CENTER,
|
||||||
kind: AtomKind::Empty,
|
kind: AtomKind::Empty,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,11 +62,27 @@ impl<'a> Atom<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a [`AtomKind::Custom`] with a specific size.
|
/// Create an [`AtomKind::Empty`] with a specific size.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```
|
||||||
|
/// # use egui::{AtomExt, AtomKind, Atom, Button, Id, __run_test_ui};
|
||||||
|
/// # use emath::Vec2;
|
||||||
|
/// # __run_test_ui(|ui| {
|
||||||
|
/// let id = Id::new("my_button");
|
||||||
|
/// let response = Button::new(("Hi!", Atom::custom(id, Vec2::splat(18.0)))).atom_ui(ui);
|
||||||
|
///
|
||||||
|
/// let rect = response.rect(id);
|
||||||
|
/// if let Some(rect) = rect {
|
||||||
|
/// ui.place(rect, Button::new("⏵"));
|
||||||
|
/// }
|
||||||
|
/// # });
|
||||||
|
/// ```
|
||||||
pub fn custom(id: Id, size: impl Into<Vec2>) -> Self {
|
pub fn custom(id: Id, size: impl Into<Vec2>) -> Self {
|
||||||
Atom {
|
Atom {
|
||||||
size: Some(size.into()),
|
size: Some(size.into()),
|
||||||
kind: AtomKind::Custom(id),
|
kind: AtomKind::Empty,
|
||||||
|
id: Some(id),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,19 +106,32 @@ impl<'a> Atom<'a> {
|
|||||||
wrap_mode = Some(TextWrapMode::Truncate);
|
wrap_mode = Some(TextWrapMode::Truncate);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (intrinsic, kind) = self
|
let id = self.id;
|
||||||
.kind
|
|
||||||
.into_sized(ui, available_size, wrap_mode, fallback_font);
|
let wrap_mode = wrap_mode.unwrap_or_else(|| ui.wrap_mode());
|
||||||
|
let IntoSizedResult {
|
||||||
|
intrinsic_size,
|
||||||
|
sized,
|
||||||
|
} = self.kind.into_sized(
|
||||||
|
ui,
|
||||||
|
IntoSizedArgs {
|
||||||
|
available_size,
|
||||||
|
wrap_mode,
|
||||||
|
fallback_font,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
let size = self
|
let size = self
|
||||||
.size
|
.size
|
||||||
.map_or_else(|| kind.size(), |s| s.at_most(self.max_size));
|
.map_or_else(|| sized.size(), |s| s.at_most(self.max_size));
|
||||||
|
|
||||||
SizedAtom {
|
SizedAtom {
|
||||||
|
id,
|
||||||
size,
|
size,
|
||||||
intrinsic_size: intrinsic.at_least(self.size.unwrap_or_default()),
|
intrinsic_size: intrinsic_size.at_least(self.size.unwrap_or_default()),
|
||||||
grow: self.grow,
|
grow: self.grow,
|
||||||
kind,
|
align: self.align,
|
||||||
|
kind: sized,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
use crate::{Atom, FontSelection, Ui};
|
use crate::{Atom, FontSelection, Id, Ui};
|
||||||
use emath::Vec2;
|
use emath::Vec2;
|
||||||
|
|
||||||
/// A trait for conveniently building [`Atom`]s.
|
/// A trait for conveniently building [`Atom`]s.
|
||||||
///
|
///
|
||||||
/// The functions are prefixed with `atom_` to avoid conflicts with e.g. [`crate::RichText::size`].
|
/// The functions are prefixed with `atom_` to avoid conflicts with e.g. [`crate::RichText::size`].
|
||||||
pub trait AtomExt<'a> {
|
pub trait AtomExt<'a> {
|
||||||
|
/// Set the [`Id`] for custom rendering.
|
||||||
|
///
|
||||||
|
/// You can get the [`crate::Rect`] with the [`Id`] from [`crate::AtomLayoutResponse`] and use a
|
||||||
|
/// [`crate::Painter`] or [`Ui::place`] to add/draw some custom content.
|
||||||
|
fn atom_id(self, id: Id) -> Atom<'a>;
|
||||||
|
|
||||||
/// Set the atom to a fixed size.
|
/// Set the atom to a fixed size.
|
||||||
///
|
///
|
||||||
/// If [`Atom::grow`] is `true`, this will be the minimum width.
|
/// If [`Atom::grow`] is `true`, this will be the minimum width.
|
||||||
@@ -63,12 +69,23 @@ pub trait AtomExt<'a> {
|
|||||||
let height = ui.fonts_mut(|f| f.row_height(&font_id));
|
let height = ui.fonts_mut(|f| f.row_height(&font_id));
|
||||||
self.atom_max_height(height)
|
self.atom_max_height(height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the [`emath::Align2`] of a single atom within its available space.
|
||||||
|
///
|
||||||
|
/// Defaults to center-center.
|
||||||
|
fn atom_align(self, align: emath::Align2) -> Atom<'a>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T> AtomExt<'a> for T
|
impl<'a, T> AtomExt<'a> for T
|
||||||
where
|
where
|
||||||
T: Into<Atom<'a>> + Sized,
|
T: Into<Atom<'a>> + Sized,
|
||||||
{
|
{
|
||||||
|
fn atom_id(self, id: Id) -> Atom<'a> {
|
||||||
|
let mut atom = self.into();
|
||||||
|
atom.id = Some(id);
|
||||||
|
atom
|
||||||
|
}
|
||||||
|
|
||||||
fn atom_size(self, size: Vec2) -> Atom<'a> {
|
fn atom_size(self, size: Vec2) -> Atom<'a> {
|
||||||
let mut atom = self.into();
|
let mut atom = self.into();
|
||||||
atom.size = Some(size);
|
atom.size = Some(size);
|
||||||
@@ -104,4 +121,10 @@ where
|
|||||||
atom.max_size.y = max_height;
|
atom.max_size.y = max_height;
|
||||||
atom
|
atom
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn atom_align(self, align: emath::Align2) -> Atom<'a> {
|
||||||
|
let mut atom = self.into();
|
||||||
|
atom.align = align;
|
||||||
|
atom
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,28 @@
|
|||||||
use crate::{FontSelection, Id, Image, ImageSource, SizedAtomKind, Ui, WidgetText};
|
use crate::{FontSelection, Image, ImageSource, SizedAtomKind, Ui, WidgetText};
|
||||||
use emath::Vec2;
|
use emath::Vec2;
|
||||||
use epaint::text::TextWrapMode;
|
use epaint::text::TextWrapMode;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
/// Args passed when sizing an [`super::Atom`]
|
||||||
|
pub struct IntoSizedArgs {
|
||||||
|
pub available_size: Vec2,
|
||||||
|
pub wrap_mode: TextWrapMode,
|
||||||
|
pub fallback_font: FontSelection,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Result returned when sizing an [`super::Atom`]
|
||||||
|
pub struct IntoSizedResult<'a> {
|
||||||
|
pub intrinsic_size: Vec2,
|
||||||
|
pub sized: SizedAtomKind<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [`AtomKind::Closure`]
|
||||||
|
// We need 'static in the result (or need to introduce another lifetime on the enum).
|
||||||
|
// Otherwise, a single 'static Atom would force the closure to be 'static.
|
||||||
|
pub type AtomClosure<'a> = Box<dyn FnOnce(&Ui, IntoSizedArgs) -> IntoSizedResult<'static> + 'a>;
|
||||||
|
|
||||||
/// The different kinds of [`crate::Atom`]s.
|
/// The different kinds of [`crate::Atom`]s.
|
||||||
#[derive(Clone, Default, Debug)]
|
#[derive(Default)]
|
||||||
pub enum AtomKind<'a> {
|
pub enum AtomKind<'a> {
|
||||||
/// Empty, that can be used with [`crate::AtomExt::atom_grow`] to reserve space.
|
/// Empty, that can be used with [`crate::AtomExt::atom_grow`] to reserve space.
|
||||||
#[default]
|
#[default]
|
||||||
@@ -38,37 +57,57 @@ pub enum AtomKind<'a> {
|
|||||||
/// default font height, which is convenient for icons.
|
/// default font height, which is convenient for icons.
|
||||||
Image(Image<'a>),
|
Image(Image<'a>),
|
||||||
|
|
||||||
/// For custom rendering.
|
/// A custom closure that produces a sized atom.
|
||||||
///
|
///
|
||||||
/// You can get the [`crate::Rect`] with the [`Id`] from [`crate::AtomLayoutResponse`] and use a
|
/// The vec2 passed in is the available size to this atom. The returned vec2 should be the
|
||||||
/// [`crate::Painter`] or [`Ui::place`] to add/draw some custom content.
|
/// preferred / intrinsic size.
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Note: This api is experimental, expect breaking changes here.
|
||||||
/// ```
|
/// When cloning, this will be cloned as [`AtomKind::Empty`].
|
||||||
/// # use egui::{AtomExt, AtomKind, Atom, Button, Id, __run_test_ui};
|
Closure(AtomClosure<'a>),
|
||||||
/// # use emath::Vec2;
|
}
|
||||||
/// # __run_test_ui(|ui| {
|
|
||||||
/// let id = Id::new("my_button");
|
impl Clone for AtomKind<'_> {
|
||||||
/// let response = Button::new(("Hi!", Atom::custom(id, Vec2::splat(18.0)))).atom_ui(ui);
|
fn clone(&self) -> Self {
|
||||||
///
|
match self {
|
||||||
/// let rect = response.rect(id);
|
AtomKind::Empty => AtomKind::Empty,
|
||||||
/// if let Some(rect) = rect {
|
AtomKind::Text(text) => AtomKind::Text(text.clone()),
|
||||||
/// ui.place(rect, Button::new("⏵"));
|
AtomKind::Image(image) => AtomKind::Image(image.clone()),
|
||||||
/// }
|
AtomKind::Closure(_) => {
|
||||||
/// # });
|
log::warn!("Cannot clone atom closures");
|
||||||
/// ```
|
AtomKind::Empty
|
||||||
Custom(Id),
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for AtomKind<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
AtomKind::Empty => write!(f, "AtomKind::Empty"),
|
||||||
|
AtomKind::Text(text) => write!(f, "AtomKind::Text({text:?})"),
|
||||||
|
AtomKind::Image(image) => write!(f, "AtomKind::Image({image:?})"),
|
||||||
|
AtomKind::Closure(_) => write!(f, "AtomKind::Closure(<closure>)"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> AtomKind<'a> {
|
impl<'a> AtomKind<'a> {
|
||||||
|
/// See [`Self::Text`]
|
||||||
pub fn text(text: impl Into<WidgetText>) -> Self {
|
pub fn text(text: impl Into<WidgetText>) -> Self {
|
||||||
AtomKind::Text(text.into())
|
AtomKind::Text(text.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// See [`Self::Image`]
|
||||||
pub fn image(image: impl Into<Image<'a>>) -> Self {
|
pub fn image(image: impl Into<Image<'a>>) -> Self {
|
||||||
AtomKind::Image(image.into())
|
AtomKind::Image(image.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// See [`Self::Closure`]
|
||||||
|
pub fn closure(func: impl FnOnce(&Ui, IntoSizedArgs) -> IntoSizedResult<'static> + 'a) -> Self {
|
||||||
|
AtomKind::Closure(Box::new(func))
|
||||||
|
}
|
||||||
|
|
||||||
/// Turn this [`AtomKind`] into a [`SizedAtomKind`].
|
/// Turn this [`AtomKind`] into a [`SizedAtomKind`].
|
||||||
///
|
///
|
||||||
/// This converts [`WidgetText`] into [`crate::Galley`] and tries to load and size [`Image`].
|
/// This converts [`WidgetText`] into [`crate::Galley`] and tries to load and size [`Image`].
|
||||||
@@ -76,23 +115,40 @@ impl<'a> AtomKind<'a> {
|
|||||||
pub fn into_sized(
|
pub fn into_sized(
|
||||||
self,
|
self,
|
||||||
ui: &Ui,
|
ui: &Ui,
|
||||||
available_size: Vec2,
|
IntoSizedArgs {
|
||||||
wrap_mode: Option<TextWrapMode>,
|
available_size,
|
||||||
fallback_font: FontSelection,
|
wrap_mode,
|
||||||
) -> (Vec2, SizedAtomKind<'a>) {
|
fallback_font,
|
||||||
|
}: IntoSizedArgs,
|
||||||
|
) -> IntoSizedResult<'a> {
|
||||||
match self {
|
match self {
|
||||||
AtomKind::Text(text) => {
|
AtomKind::Text(text) => {
|
||||||
let wrap_mode = wrap_mode.unwrap_or_else(|| ui.wrap_mode());
|
|
||||||
let galley = text.into_galley(ui, Some(wrap_mode), available_size.x, fallback_font);
|
let galley = text.into_galley(ui, Some(wrap_mode), available_size.x, fallback_font);
|
||||||
(galley.intrinsic_size(), SizedAtomKind::Text(galley))
|
IntoSizedResult {
|
||||||
|
intrinsic_size: galley.intrinsic_size(),
|
||||||
|
sized: SizedAtomKind::Text(galley),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AtomKind::Image(image) => {
|
AtomKind::Image(image) => {
|
||||||
let size = image.load_and_calc_size(ui, available_size);
|
let size = image.load_and_calc_size(ui, available_size);
|
||||||
let size = size.unwrap_or(Vec2::ZERO);
|
let size = size.unwrap_or(Vec2::ZERO);
|
||||||
(size, SizedAtomKind::Image(image, size))
|
IntoSizedResult {
|
||||||
|
intrinsic_size: size,
|
||||||
|
sized: SizedAtomKind::Image { image, size },
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AtomKind::Custom(id) => (Vec2::ZERO, SizedAtomKind::Custom(id)),
|
AtomKind::Empty => IntoSizedResult {
|
||||||
AtomKind::Empty => (Vec2::ZERO, SizedAtomKind::Empty),
|
intrinsic_size: Vec2::ZERO,
|
||||||
|
sized: SizedAtomKind::Empty { size: None },
|
||||||
|
},
|
||||||
|
AtomKind::Closure(func) => func(
|
||||||
|
ui,
|
||||||
|
IntoSizedArgs {
|
||||||
|
available_size,
|
||||||
|
wrap_mode,
|
||||||
|
fallback_font,
|
||||||
|
},
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ pub struct AtomLayout<'a> {
|
|||||||
fallback_text_color: Option<Color32>,
|
fallback_text_color: Option<Color32>,
|
||||||
fallback_font: Option<FontSelection>,
|
fallback_font: Option<FontSelection>,
|
||||||
min_size: Vec2,
|
min_size: Vec2,
|
||||||
|
max_size: Vec2,
|
||||||
wrap_mode: Option<TextWrapMode>,
|
wrap_mode: Option<TextWrapMode>,
|
||||||
align2: Option<Align2>,
|
align2: Option<Align2>,
|
||||||
}
|
}
|
||||||
@@ -59,6 +60,7 @@ impl<'a> AtomLayout<'a> {
|
|||||||
fallback_text_color: None,
|
fallback_text_color: None,
|
||||||
fallback_font: None,
|
fallback_font: None,
|
||||||
min_size: Vec2::ZERO,
|
min_size: Vec2::ZERO,
|
||||||
|
max_size: Vec2::INFINITY,
|
||||||
wrap_mode: None,
|
wrap_mode: None,
|
||||||
align2: None,
|
align2: None,
|
||||||
}
|
}
|
||||||
@@ -113,6 +115,33 @@ impl<'a> AtomLayout<'a> {
|
|||||||
self
|
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 [`Id`] used to allocate a [`Response`].
|
/// Set the [`Id`] used to allocate a [`Response`].
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn id(mut self, id: Id) -> Self {
|
pub fn id(mut self, id: Id) -> Self {
|
||||||
@@ -161,6 +190,7 @@ impl<'a> AtomLayout<'a> {
|
|||||||
sense,
|
sense,
|
||||||
fallback_text_color,
|
fallback_text_color,
|
||||||
min_size,
|
min_size,
|
||||||
|
mut max_size,
|
||||||
wrap_mode,
|
wrap_mode,
|
||||||
align2,
|
align2,
|
||||||
fallback_font,
|
fallback_font,
|
||||||
@@ -190,8 +220,16 @@ impl<'a> AtomLayout<'a> {
|
|||||||
fallback_text_color.unwrap_or_else(|| ui.style().visuals.text_color());
|
fallback_text_color.unwrap_or_else(|| ui.style().visuals.text_color());
|
||||||
let gap = gap.unwrap_or_else(|| ui.spacing().icon_spacing);
|
let gap = gap.unwrap_or_else(|| ui.spacing().icon_spacing);
|
||||||
|
|
||||||
|
// max_size has no effect in justified layouts. If we'd limit the available size here,
|
||||||
|
// the content would be sized differently than the frame which would look weird.
|
||||||
|
if ui.layout().horizontal_justify() {
|
||||||
|
max_size.x = f32::INFINITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
let available_size = ui.available_size().at_most(max_size);
|
||||||
|
|
||||||
// The size available for the content
|
// The size available for the content
|
||||||
let available_inner_size = ui.available_size() - frame.total_margin().sum();
|
let available_inner_size = available_size - frame.total_margin().sum();
|
||||||
|
|
||||||
let mut desired_width = 0.0;
|
let mut desired_width = 0.0;
|
||||||
|
|
||||||
@@ -321,7 +359,7 @@ impl<'atom> AllocatedAtomLayout<'atom> {
|
|||||||
|
|
||||||
pub fn iter_images(&self) -> impl Iterator<Item = &Image<'atom>> {
|
pub fn iter_images(&self) -> impl Iterator<Item = &Image<'atom>> {
|
||||||
self.iter_kinds().filter_map(|kind| {
|
self.iter_kinds().filter_map(|kind| {
|
||||||
if let SizedAtomKind::Image(image, _) = kind {
|
if let SizedAtomKind::Image { image, size: _ } = kind {
|
||||||
Some(image)
|
Some(image)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -331,7 +369,7 @@ impl<'atom> AllocatedAtomLayout<'atom> {
|
|||||||
|
|
||||||
pub fn iter_images_mut(&mut self) -> impl Iterator<Item = &mut Image<'atom>> {
|
pub fn iter_images_mut(&mut self) -> impl Iterator<Item = &mut Image<'atom>> {
|
||||||
self.iter_kinds_mut().filter_map(|kind| {
|
self.iter_kinds_mut().filter_map(|kind| {
|
||||||
if let SizedAtomKind::Image(image, _) = kind {
|
if let SizedAtomKind::Image { image, size: _ } = kind {
|
||||||
Some(image)
|
Some(image)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -373,8 +411,11 @@ impl<'atom> AllocatedAtomLayout<'atom> {
|
|||||||
F: FnMut(Image<'atom>) -> Image<'atom>,
|
F: FnMut(Image<'atom>) -> Image<'atom>,
|
||||||
{
|
{
|
||||||
self.map_kind(|kind| {
|
self.map_kind(|kind| {
|
||||||
if let SizedAtomKind::Image(image, size) = kind {
|
if let SizedAtomKind::Image { image, size } = kind {
|
||||||
SizedAtomKind::Image(f(image), size)
|
SizedAtomKind::Image {
|
||||||
|
image: f(image),
|
||||||
|
size,
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
kind
|
kind
|
||||||
}
|
}
|
||||||
@@ -422,25 +463,24 @@ impl<'atom> AllocatedAtomLayout<'atom> {
|
|||||||
.with_min_x(cursor)
|
.with_min_x(cursor)
|
||||||
.with_max_x(cursor + size.x + growth);
|
.with_max_x(cursor + size.x + growth);
|
||||||
cursor = frame.right() + gap;
|
cursor = frame.right() + gap;
|
||||||
|
let rect = sized.align.align_size_within_rect(size, frame);
|
||||||
|
|
||||||
let align = Align2::CENTER_CENTER;
|
if let Some(id) = sized.id {
|
||||||
let rect = align.align_size_within_rect(size, frame);
|
debug_assert!(
|
||||||
|
!response.custom_rects.iter().any(|(i, _)| *i == id),
|
||||||
|
"Duplicate custom id"
|
||||||
|
);
|
||||||
|
response.custom_rects.push((id, rect));
|
||||||
|
}
|
||||||
|
|
||||||
match sized.kind {
|
match sized.kind {
|
||||||
SizedAtomKind::Text(galley) => {
|
SizedAtomKind::Text(galley) => {
|
||||||
ui.painter().galley(rect.min, galley, fallback_text_color);
|
ui.painter().galley(rect.min, galley, fallback_text_color);
|
||||||
}
|
}
|
||||||
SizedAtomKind::Image(image, _) => {
|
SizedAtomKind::Image { image, size: _ } => {
|
||||||
image.paint_at(ui, rect);
|
image.paint_at(ui, rect);
|
||||||
}
|
}
|
||||||
SizedAtomKind::Custom(id) => {
|
SizedAtomKind::Empty { .. } => {}
|
||||||
debug_assert!(
|
|
||||||
!response.custom_rects.iter().any(|(i, _)| *i == id),
|
|
||||||
"Duplicate custom id"
|
|
||||||
);
|
|
||||||
response.custom_rects.push((id, rect));
|
|
||||||
}
|
|
||||||
SizedAtomKind::Empty => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,7 +490,7 @@ impl<'atom> AllocatedAtomLayout<'atom> {
|
|||||||
|
|
||||||
/// Response from a [`AtomLayout::show`] or [`AllocatedAtomLayout::paint`].
|
/// Response from a [`AtomLayout::show`] or [`AllocatedAtomLayout::paint`].
|
||||||
///
|
///
|
||||||
/// Use [`AtomLayoutResponse::rect`] to get the response rects from [`AtomKind::Custom`].
|
/// Use [`AtomLayoutResponse::rect`] to get the response rects from [`crate::Atom::custom`].
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct AtomLayoutResponse {
|
pub struct AtomLayoutResponse {
|
||||||
pub response: Response,
|
pub response: Response,
|
||||||
@@ -470,7 +510,7 @@ impl AtomLayoutResponse {
|
|||||||
self.custom_rects.iter().copied()
|
self.custom_rects.iter().copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Use this together with [`AtomKind::Custom`] to add custom painting / child widgets.
|
/// 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.
|
/// NOTE: Don't `unwrap` rects, they might be empty when the widget is not visible.
|
||||||
pub fn rect(&self, id: Id) -> Option<Rect> {
|
pub fn rect(&self, id: Id) -> Option<Rect> {
|
||||||
|
|||||||
@@ -21,21 +21,24 @@ impl<'a> Atoms<'a> {
|
|||||||
self.0.push(atom.into());
|
self.0.push(atom.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extend the list of atoms by appending more atoms to the right side.
|
||||||
|
///
|
||||||
|
/// If you have weird lifetime issues with this, use [`Self::push_right`] in a loop instead.
|
||||||
|
pub fn extend_right(&mut self, atoms: Self) {
|
||||||
|
self.0.extend(atoms.0);
|
||||||
|
}
|
||||||
|
|
||||||
/// Insert a new [`Atom`] at the beginning of the list (left side).
|
/// Insert a new [`Atom`] at the beginning of the list (left side).
|
||||||
pub fn push_left(&mut self, atom: impl Into<Atom<'a>>) {
|
pub fn push_left(&mut self, atom: impl Into<Atom<'a>>) {
|
||||||
self.0.insert(0, atom.into());
|
self.0.insert(0, atom.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert atoms at the beginning of the list (left side).
|
/// Extend the list of atoms by prepending more atoms to the left side.
|
||||||
pub fn extend_left(&mut self, atoms: impl IntoAtoms<'a>) {
|
///
|
||||||
let mut left = atoms.into_atoms();
|
/// If you have weird lifetime issues with this, use [`Self::push_left`] in a loop instead.
|
||||||
left.0.append(&mut self.0);
|
pub fn extend_left(&mut self, mut atoms: Self) {
|
||||||
*self = left;
|
std::mem::swap(&mut atoms.0, &mut self.0);
|
||||||
}
|
self.0.extend(atoms.0);
|
||||||
|
|
||||||
/// Insert atoms at the end of the list (right side).
|
|
||||||
pub fn extend_right(&mut self, atoms: impl IntoAtoms<'a>) {
|
|
||||||
self.0.append(&mut atoms.into_atoms().0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Concatenate and return the text contents.
|
/// Concatenate and return the text contents.
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ use emath::Vec2;
|
|||||||
/// A [`crate::Atom`] which has been sized.
|
/// A [`crate::Atom`] which has been sized.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct SizedAtom<'a> {
|
pub struct SizedAtom<'a> {
|
||||||
|
pub id: Option<crate::Id>,
|
||||||
|
|
||||||
pub(crate) grow: bool,
|
pub(crate) grow: bool,
|
||||||
|
|
||||||
/// The size of the atom.
|
/// The size of the atom.
|
||||||
@@ -15,6 +17,9 @@ pub struct SizedAtom<'a> {
|
|||||||
/// Intrinsic size of the atom. This is used to calculate `Response::intrinsic_size`.
|
/// Intrinsic size of the atom. This is used to calculate `Response::intrinsic_size`.
|
||||||
pub intrinsic_size: Vec2,
|
pub intrinsic_size: Vec2,
|
||||||
|
|
||||||
|
/// How will the atom be aligned in its available space?
|
||||||
|
pub align: emath::Align2,
|
||||||
|
|
||||||
pub kind: SizedAtomKind<'a>,
|
pub kind: SizedAtomKind<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
use crate::{Id, Image};
|
use crate::Image;
|
||||||
use emath::Vec2;
|
use emath::Vec2;
|
||||||
use epaint::Galley;
|
use epaint::Galley;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// A sized [`crate::AtomKind`].
|
/// A sized [`crate::AtomKind`].
|
||||||
#[derive(Clone, Default, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum SizedAtomKind<'a> {
|
pub enum SizedAtomKind<'a> {
|
||||||
#[default]
|
Empty { size: Option<Vec2> },
|
||||||
Empty,
|
|
||||||
Text(Arc<Galley>),
|
Text(Arc<Galley>),
|
||||||
Image(Image<'a>, Vec2),
|
Image { image: Image<'a>, size: Vec2 },
|
||||||
Custom(Id),
|
}
|
||||||
|
|
||||||
|
impl Default for SizedAtomKind<'_> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Empty { size: None }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SizedAtomKind<'_> {
|
impl SizedAtomKind<'_> {
|
||||||
@@ -18,8 +22,8 @@ impl SizedAtomKind<'_> {
|
|||||||
pub fn size(&self) -> Vec2 {
|
pub fn size(&self) -> Vec2 {
|
||||||
match self {
|
match self {
|
||||||
SizedAtomKind::Text(galley) => galley.size(),
|
SizedAtomKind::Text(galley) => galley.size(),
|
||||||
SizedAtomKind::Image(_, size) => *size,
|
SizedAtomKind::Image { image: _, size } => *size,
|
||||||
SizedAtomKind::Empty | SizedAtomKind::Custom(_) => Vec2::ZERO,
|
SizedAtomKind::Empty { size } => size.unwrap_or_default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -166,14 +166,14 @@ impl<'a> DragValue<'a> {
|
|||||||
/// Show a prefix before the number, e.g. "x: "
|
/// Show a prefix before the number, e.g. "x: "
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn prefix(mut self, prefix: impl IntoAtoms<'a>) -> Self {
|
pub fn prefix(mut self, prefix: impl IntoAtoms<'a>) -> Self {
|
||||||
self.atoms.extend_left(prefix);
|
self.atoms.extend_left(prefix.into_atoms());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a suffix to the number, this can be e.g. a unit ("°" or " m")
|
/// Add a suffix to the number, this can be e.g. a unit ("°" or " m")
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn suffix(mut self, suffix: impl IntoAtoms<'a>) -> Self {
|
pub fn suffix(mut self, suffix: impl IntoAtoms<'a>) -> Self {
|
||||||
self.atoms.extend_right(suffix);
|
self.atoms.extend_right(suffix.into_atoms());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,18 +447,15 @@ impl Widget for DragValue<'_> {
|
|||||||
let mut past_value = false;
|
let mut past_value = false;
|
||||||
let atom_id = Id::new(Self::ATOM_ID);
|
let atom_id = Id::new(Self::ATOM_ID);
|
||||||
for atom in atoms.iter() {
|
for atom in atoms.iter() {
|
||||||
match &atom.kind {
|
if atom.id == Some(atom_id) {
|
||||||
AtomKind::Custom(id) if *id == atom_id => {
|
past_value = true;
|
||||||
past_value = true;
|
}
|
||||||
|
if let AtomKind::Text(text) = &atom.kind {
|
||||||
|
if past_value {
|
||||||
|
suffix_text.push_str(text.text());
|
||||||
|
} else {
|
||||||
|
prefix_text.push_str(text.text());
|
||||||
}
|
}
|
||||||
AtomKind::Text(text) => {
|
|
||||||
if past_value {
|
|
||||||
suffix_text.push_str(text.text());
|
|
||||||
} else {
|
|
||||||
prefix_text.push_str(text.text());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -605,9 +602,7 @@ impl Widget for DragValue<'_> {
|
|||||||
response
|
response
|
||||||
} else {
|
} else {
|
||||||
atoms.map_atoms(|atom| {
|
atoms.map_atoms(|atom| {
|
||||||
if let AtomKind::Custom(id) = atom.kind
|
if atom.id == Some(atom_id) {
|
||||||
&& id == atom_id
|
|
||||||
{
|
|
||||||
RichText::new(value_text.clone())
|
RichText::new(value_text.clone())
|
||||||
.text_style(text_style.clone())
|
.text_style(text_style.clone())
|
||||||
.into()
|
.into()
|
||||||
|
|||||||
Reference in New Issue
Block a user