-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Add AtomLayout
, abstracing layouting within widgets
#5830
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
82 commits
Select commit
Hold shift + click to select a range
48254b0
Experiment with `WidgetLayout`
lucasmerlin fd78ea9
Finish WidgetLayout prototype
lucasmerlin 69dc8e0
Add todos
lucasmerlin ea2bff4
Add Atomics, IntoAtomics, Atomic, AtomicKind, implement Button with n…
lucasmerlin df79e56
Button improvements, implement checkbox
lucasmerlin cfec476
Implement shrinking
lucasmerlin 26012cd
Tons of small improvements to make the new button implementation matc…
lucasmerlin c327d99
Improve default font height based on https://github.com/emilk/egui/pu…
lucasmerlin 160a1f5
Allow customizing id
lucasmerlin 6046323
Set preferred size
lucasmerlin 324d20a
Handle min_size for intrinsic size
lucasmerlin 6191e28
Correctly handle TextWrapMode
lucasmerlin f3061ff
Add align2 option
lucasmerlin db3e3c7
Handle a_size correctly
lucasmerlin 3b13333
Minor improvements
lucasmerlin 2a207f7
Implement selectable button
lucasmerlin b33e4d2
Move wrap mode handling to atomics
lucasmerlin c7afe92
Make button hold Atomics instead of AtomicLayout and rename Kind::Gro…
lucasmerlin 6bcf455
Use weak_bg_fill, fixing menus
lucasmerlin 2d24b48
Implement image_tint_follows_text_color
lucasmerlin 1d36767
Rename to AtomicLayout
lucasmerlin 61a513a
Split WidgetRect in allocate and paint. Make checkbox pixel perfect. …
lucasmerlin 8907d90
Update radio button based on checkbox
lucasmerlin 84b14e2
Clippy fixes
lucasmerlin 6b8ee5c
Revert hello world simple changes
lucasmerlin a9c466b
Add some tests for AtomicLayout
lucasmerlin f643045
Update snapshots
lucasmerlin f0176d4
All the docs!
lucasmerlin 5b84d90
Fixes
lucasmerlin d02fc19
Remove commented-out code
lucasmerlin 285c065
Fix grow count being off
lucasmerlin 31b2974
Fix doc links
lucasmerlin 55bbf2b
Revert style change
lucasmerlin 3303b27
Sprinkle some more atomics
lucasmerlin 2524f44
Split atomics into several modules
lucasmerlin 2279b93
Fix left <-> right mixup
lucasmerlin 285d737
replace matches
lucasmerlin 59a3361
Small vec optimization
lucasmerlin a43ff81
Merge branch 'master' into lucas/experiments/widget_layout
lucasmerlin 8485ec4
Some fixes after review
lucasmerlin ef19ccd
Rename a_* to atom_*
lucasmerlin eb9ce42
Rename front/back to left/right and impl deref instead of duplicating…
lucasmerlin 75bc01f
Merge branch 'master' into lucas/experiments/widget_layout
lucasmerlin 349c66b
Clippy fixes
lucasmerlin ce5cc49
Merge branch 'master' into lucas/experiments/widget_layout
lucasmerlin f1e66e1
Clippy
lucasmerlin 44729b0
Lint
lucasmerlin f3cc4d2
Move AtomicExt
lucasmerlin 545f533
Add comment explaining how atomic size relates to grow / shrink
lucasmerlin b90f01b
Explain how truncation works
lucasmerlin ca5027d
Add iterator utilities
lucasmerlin 2eb39d8
Remove default font size image height behavior from AtomicLayout and …
lucasmerlin a0de60c
Update snapshots
lucasmerlin 1d537d5
Import
lucasmerlin bda7816
Implement max size for Text
lucasmerlin a7b78e4
More docs
lucasmerlin f9ac3b6
Impl debug for WidgetText
lucasmerlin a74f3dd
Make grow private in SizedAtomic
lucasmerlin 3a45786
Fixes from review
lucasmerlin e9c89ba
Improve docs
lucasmerlin 90ac0d5
Construct AtomicLayout in Button constructor for performance gains
lucasmerlin 6e823be
Docs
lucasmerlin 34351a3
Merge branch 'main' into lucas/experiments/widget_layout
lucasmerlin 615d22c
Ooops set correct sense
lucasmerlin e258817
Fix doctest
lucasmerlin e00d463
Typo
lucasmerlin 6d17866
Fix left/right mixup and add wip test
lucasmerlin e6b3d52
Remove redundant size Vec2 from AtomicKind::Custom
lucasmerlin 7736c5b
Add more tests
lucasmerlin 689b65e
Ensure max_size limits size
lucasmerlin 443eca8
Change text() to return Cow<str>
lucasmerlin e3af0fb
Rename atomic to atom
lucasmerlin 17f61c3
Rename IntoAtoms params to atoms
lucasmerlin fae0e91
Doc
lucasmerlin 757b5cd
Merge branch 'main' into lucas/experiments/widget_layout
lucasmerlin dcc6bec
Rename get_rect to rect
lucasmerlin 21ec937
Remove clippy expect
lucasmerlin 5c780e7
Space
lucasmerlin 6919bf3
Docs
lucasmerlin 356026c
Update snapshots
lucasmerlin 168fd86
Add comment
lucasmerlin 6496041
Fix doc
lucasmerlin File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1263,6 +1263,7 @@ dependencies = [ | |
"profiling", | ||
"ron", | ||
"serde", | ||
"smallvec", | ||
"unicode-segmentation", | ||
] | ||
|
||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
use crate::{AtomKind, Id, SizedAtom, Ui}; | ||
use emath::{NumExt as _, Vec2}; | ||
use epaint::text::TextWrapMode; | ||
|
||
/// A low-level ui building block. | ||
/// | ||
/// Implements [`From`] for [`String`], [`str`], [`crate::Image`] and much more for convenience. | ||
/// You can directly call the `atom_*` methods on anything that implements `Into<Atom>`. | ||
/// ``` | ||
/// # use egui::{Image, emath::Vec2}; | ||
/// use egui::AtomExt as _; | ||
/// let string_atom = "Hello".atom_grow(true); | ||
/// let image_atom = Image::new("some_image_url").atom_size(Vec2::splat(20.0)); | ||
/// ``` | ||
#[derive(Clone, Debug)] | ||
pub struct Atom<'a> { | ||
/// See [`crate::AtomExt::atom_size`] | ||
pub size: Option<Vec2>, | ||
|
||
/// See [`crate::AtomExt::atom_max_size`] | ||
pub max_size: Vec2, | ||
|
||
/// See [`crate::AtomExt::atom_grow`] | ||
pub grow: bool, | ||
|
||
/// See [`crate::AtomExt::atom_shrink`] | ||
pub shrink: bool, | ||
|
||
/// The atom type | ||
pub kind: AtomKind<'a>, | ||
} | ||
|
||
impl Default for Atom<'_> { | ||
fn default() -> Self { | ||
Atom { | ||
size: None, | ||
max_size: Vec2::INFINITY, | ||
grow: false, | ||
shrink: false, | ||
kind: AtomKind::Empty, | ||
} | ||
} | ||
} | ||
|
||
impl<'a> Atom<'a> { | ||
/// Create an empty [`Atom`] marked as `grow`. | ||
/// | ||
/// This will expand in size, allowing all preceding atoms to be left-aligned, | ||
/// and all following atoms to be right-aligned | ||
pub fn grow() -> Self { | ||
Atom { | ||
grow: true, | ||
..Default::default() | ||
} | ||
} | ||
|
||
/// Create a [`AtomKind::Custom`] with a specific size. | ||
pub fn custom(id: Id, size: impl Into<Vec2>) -> Self { | ||
Atom { | ||
size: Some(size.into()), | ||
kind: AtomKind::Custom(id), | ||
..Default::default() | ||
} | ||
} | ||
|
||
/// Turn this into a [`SizedAtom`]. | ||
pub fn into_sized( | ||
self, | ||
ui: &Ui, | ||
mut available_size: Vec2, | ||
mut wrap_mode: Option<TextWrapMode>, | ||
) -> SizedAtom<'a> { | ||
if !self.shrink && self.max_size.x.is_infinite() { | ||
wrap_mode = Some(TextWrapMode::Extend); | ||
} | ||
available_size = available_size.at_most(self.max_size); | ||
if let Some(size) = self.size { | ||
available_size = available_size.at_most(size); | ||
} | ||
if self.max_size.x.is_finite() { | ||
wrap_mode = Some(TextWrapMode::Truncate); | ||
} | ||
|
||
let (preferred, kind) = self.kind.into_sized(ui, available_size, wrap_mode); | ||
|
||
let size = self | ||
.size | ||
.map_or_else(|| kind.size(), |s| s.at_most(self.max_size)); | ||
|
||
SizedAtom { | ||
size, | ||
preferred_size: preferred, | ||
grow: self.grow, | ||
kind, | ||
} | ||
} | ||
} | ||
|
||
impl<'a, T> From<T> for Atom<'a> | ||
where | ||
T: Into<AtomKind<'a>>, | ||
{ | ||
fn from(value: T) -> Self { | ||
Atom { | ||
kind: value.into(), | ||
..Default::default() | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
use crate::{Atom, FontSelection, Ui}; | ||
use emath::Vec2; | ||
|
||
/// A trait for conveniently building [`Atom`]s. | ||
/// | ||
/// The functions are prefixed with `atom_` to avoid conflicts with e.g. [`crate::RichText::size`]. | ||
pub trait AtomExt<'a> { | ||
/// Set the atom to a fixed size. | ||
/// | ||
/// If [`Atom::grow`] is `true`, this will be the minimum width. | ||
/// If [`Atom::shrink`] is `true`, this will be the maximum width. | ||
/// If both are true, the width will have no effect. | ||
/// | ||
/// [`Self::atom_max_size`] will limit size. | ||
/// | ||
/// See [`crate::AtomKind`] docs to see how the size affects the different types. | ||
fn atom_size(self, size: Vec2) -> Atom<'a>; | ||
|
||
/// Grow this atom to the available space. | ||
/// | ||
/// This will affect the size of the [`Atom`] in the main direction. Since | ||
/// [`crate::AtomLayout`] today only supports horizontal layout, it will affect the width. | ||
/// | ||
/// You can also combine this with [`Self::atom_shrink`] to make it always take exactly the | ||
/// remaining space. | ||
fn atom_grow(self, grow: bool) -> Atom<'a>; | ||
|
||
/// Shrink this atom if there isn't enough space. | ||
/// | ||
/// This will affect the size of the [`Atom`] in the main direction. Since | ||
/// [`crate::AtomLayout`] today only supports horizontal layout, it will affect the width. | ||
/// | ||
/// NOTE: Only a single [`Atom`] may shrink for each widget. | ||
/// | ||
/// If no atom was set to shrink and `wrap_mode != TextWrapMode::Extend`, the first | ||
/// `AtomKind::Text` is set to shrink. | ||
fn atom_shrink(self, shrink: bool) -> Atom<'a>; | ||
|
||
/// Set the maximum size of this atom. | ||
/// | ||
/// Will not affect the space taken by `grow` (All atoms marked as grow will always grow | ||
/// equally to fill the available space). | ||
fn atom_max_size(self, max_size: Vec2) -> Atom<'a>; | ||
|
||
/// Set the maximum width of this atom. | ||
/// | ||
/// Will not affect the space taken by `grow` (All atoms marked as grow will always grow | ||
/// equally to fill the available space). | ||
fn atom_max_width(self, max_width: f32) -> Atom<'a>; | ||
|
||
/// Set the maximum height of this atom. | ||
fn atom_max_height(self, max_height: f32) -> Atom<'a>; | ||
|
||
/// Set the max height of this atom to match the font size. | ||
/// | ||
/// This is useful for e.g. limiting the height of icons in buttons. | ||
fn atom_max_height_font_size(self, ui: &Ui) -> Atom<'a> | ||
where | ||
Self: Sized, | ||
{ | ||
let font_selection = FontSelection::default(); | ||
let font_id = font_selection.resolve(ui.style()); | ||
let height = ui.fonts(|f| f.row_height(&font_id)); | ||
self.atom_max_height(height) | ||
} | ||
} | ||
|
||
impl<'a, T> AtomExt<'a> for T | ||
where | ||
T: Into<Atom<'a>> + Sized, | ||
{ | ||
fn atom_size(self, size: Vec2) -> Atom<'a> { | ||
let mut atom = self.into(); | ||
atom.size = Some(size); | ||
atom | ||
} | ||
|
||
fn atom_grow(self, grow: bool) -> Atom<'a> { | ||
let mut atom = self.into(); | ||
atom.grow = grow; | ||
atom | ||
} | ||
|
||
fn atom_shrink(self, shrink: bool) -> Atom<'a> { | ||
let mut atom = self.into(); | ||
atom.shrink = shrink; | ||
atom | ||
} | ||
|
||
fn atom_max_size(self, max_size: Vec2) -> Atom<'a> { | ||
let mut atom = self.into(); | ||
atom.max_size = max_size; | ||
atom | ||
} | ||
|
||
fn atom_max_width(self, max_width: f32) -> Atom<'a> { | ||
let mut atom = self.into(); | ||
atom.max_size.x = max_width; | ||
atom | ||
} | ||
|
||
fn atom_max_height(self, max_height: f32) -> Atom<'a> { | ||
let mut atom = self.into(); | ||
atom.max_size.y = max_height; | ||
atom | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
use crate::{Id, Image, ImageSource, SizedAtomKind, TextStyle, Ui, WidgetText}; | ||
use emath::Vec2; | ||
use epaint::text::TextWrapMode; | ||
|
||
/// The different kinds of [`crate::Atom`]s. | ||
#[derive(Clone, Default, Debug)] | ||
pub enum AtomKind<'a> { | ||
/// Empty, that can be used with [`crate::AtomExt::atom_grow`] to reserve space. | ||
#[default] | ||
Empty, | ||
|
||
/// Text atom. | ||
/// | ||
/// Truncation within [`crate::AtomLayout`] works like this: | ||
/// - | ||
/// - if `wrap_mode` is not Extend | ||
/// - if no atom is `shrink` | ||
/// - the first text atom is selected and will be marked as `shrink` | ||
/// - the atom marked as `shrink` will shrink / wrap based on the selected wrap mode | ||
/// - any other text atoms will have `wrap_mode` extend | ||
/// - if `wrap_mode` is extend, Text will extend as expected. | ||
/// | ||
/// Unless [`crate::AtomExt::atom_max_width`] is set, `wrap_mode` should only be set via [`crate::Style`] or | ||
/// [`crate::AtomLayout::wrap_mode`], as setting a wrap mode on a [`WidgetText`] atom | ||
/// that is not `shrink` will have unexpected results. | ||
/// | ||
/// The size is determined by converting the [`WidgetText`] into a galley and using the galleys | ||
/// size. You can use [`crate::AtomExt::atom_size`] to override this, and [`crate::AtomExt::atom_max_width`] | ||
/// to limit the width (Causing the text to wrap or truncate, depending on the `wrap_mode`. | ||
/// [`crate::AtomExt::atom_max_height`] has no effect on text. | ||
Text(WidgetText), | ||
|
||
/// Image atom. | ||
/// | ||
/// By default the size is determined via [`Image::calc_size`]. | ||
/// You can use [`crate::AtomExt::atom_max_size`] or [`crate::AtomExt::atom_size`] to customize the size. | ||
/// There is also a helper [`crate::AtomExt::atom_max_height_font_size`] to set the max height to the | ||
/// default font height, which is convenient for icons. | ||
Image(Image<'a>), | ||
|
||
/// For custom rendering. | ||
/// | ||
/// You can get the [`crate::Rect`] with the [`Id`] from [`crate::AtomLayoutResponse`] and use a | ||
/// [`crate::Painter`] or [`Ui::put`] to add/draw some custom content. | ||
/// | ||
/// 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.put(rect, Button::new("⏵")); | ||
/// } | ||
/// # }); | ||
/// ``` | ||
Custom(Id), | ||
} | ||
|
||
impl<'a> AtomKind<'a> { | ||
pub fn text(text: impl Into<WidgetText>) -> Self { | ||
AtomKind::Text(text.into()) | ||
} | ||
|
||
pub fn image(image: impl Into<Image<'a>>) -> Self { | ||
AtomKind::Image(image.into()) | ||
} | ||
|
||
/// Turn this [`AtomKind`] into a [`SizedAtomKind`]. | ||
/// | ||
/// This converts [`WidgetText`] into [`crate::Galley`] and tries to load and size [`Image`]. | ||
/// The first returned argument is the preferred size. | ||
pub fn into_sized( | ||
self, | ||
ui: &Ui, | ||
available_size: Vec2, | ||
wrap_mode: Option<TextWrapMode>, | ||
) -> (Vec2, SizedAtomKind<'a>) { | ||
match self { | ||
AtomKind::Text(text) => { | ||
let galley = text.into_galley(ui, wrap_mode, available_size.x, TextStyle::Button); | ||
( | ||
galley.size(), // TODO(#5762): calculate the preferred size | ||
SizedAtomKind::Text(galley), | ||
) | ||
} | ||
AtomKind::Image(image) => { | ||
let size = image.load_and_calc_size(ui, available_size); | ||
let size = size.unwrap_or(Vec2::ZERO); | ||
(size, SizedAtomKind::Image(image, size)) | ||
} | ||
AtomKind::Custom(id) => (Vec2::ZERO, SizedAtomKind::Custom(id)), | ||
AtomKind::Empty => (Vec2::ZERO, SizedAtomKind::Empty), | ||
} | ||
} | ||
} | ||
|
||
impl<'a> From<ImageSource<'a>> for AtomKind<'a> { | ||
fn from(value: ImageSource<'a>) -> Self { | ||
AtomKind::Image(value.into()) | ||
} | ||
} | ||
|
||
impl<'a> From<Image<'a>> for AtomKind<'a> { | ||
fn from(value: Image<'a>) -> Self { | ||
AtomKind::Image(value) | ||
} | ||
} | ||
|
||
impl<T> From<T> for AtomKind<'_> | ||
where | ||
T: Into<WidgetText>, | ||
{ | ||
fn from(value: T) -> Self { | ||
AtomKind::Text(value.into()) | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: sort
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mnopqrstu... It should be sorted correctly