Merge branch 'master' into feature/test-recorder

This commit is contained in:
Héctor Ramón Jiménez 2025-08-29 04:25:52 +02:00
commit 9e81c2b9e8
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
88 changed files with 1225 additions and 1158 deletions

View file

@ -263,3 +263,16 @@ impl From<Radius> for [f32; 4] {
]
}
}
impl std::ops::Mul<f32> for Radius {
type Output = Self;
fn mul(self, scale: f32) -> Self::Output {
Self {
top_left: self.top_left * scale,
top_right: self.top_right * scale,
bottom_right: self.bottom_right * scale,
bottom_left: self.bottom_left * scale,
}
}
}

View file

@ -197,7 +197,7 @@ impl Color {
}
/// Returns the relative luminance of the [`Color`].
/// https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
/// <https://www.w3.org/TR/WCAG21/#dfn-relative-luminance>
pub fn relative_luminance(self) -> f32 {
let linear = self.into_linear();
0.2126 * linear[0] + 0.7152 * linear[1] + 0.0722 * linear[2]

View file

@ -291,7 +291,7 @@ where
}
fn layout(
&self,
&mut self,
tree: &mut Tree,
renderer: &Renderer,
limits: &layout::Limits,
@ -300,7 +300,7 @@ where
}
fn operate(
&self,
&mut self,
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
@ -426,7 +426,7 @@ where
}
fn layout(
&self,
&mut self,
tree: &mut Tree,
renderer: &Renderer,
limits: &layout::Limits,
@ -435,7 +435,7 @@ where
}
fn operate(
&self,
&mut self,
state: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
@ -554,7 +554,7 @@ where
}
fn layout(
&self,
&mut self,
_tree: &mut Tree,
_renderer: &Renderer,
_limits: &layout::Limits,

View file

@ -53,7 +53,7 @@ impl<'a> Layout<'a> {
}
}
/// Returns an iterator over the [`Layout`] of the children of a [`Node`].
/// Returns an iterator over the children of this [`Layout`].
pub fn children(self) -> impl DoubleEndedIterator<Item = Layout<'a>> {
self.node.children().iter().map(move |node| {
Layout::with_offset(
@ -62,6 +62,19 @@ impl<'a> Layout<'a> {
)
})
}
/// Returns the [`Layout`] of the child at the given index.
///
/// This can be useful if you ever need to access children out of order
/// for layering purposes.
///
/// # Panics
/// Panics if index is out of bounds.
pub fn child(self, index: usize) -> Layout<'a> {
let node = &self.node.children()[index];
Layout::with_offset(Vector::new(self.position.x, self.position.y), node)
}
}
/// Produces a [`Node`] with two children nodes one right next to each other.

View file

@ -68,7 +68,7 @@ pub fn resolve<Message, Theme, Renderer>(
padding: Padding,
spacing: f32,
align_items: Alignment,
items: &[Element<'_, Message, Theme, Renderer>],
items: &mut [Element<'_, Message, Theme, Renderer>],
trees: &mut [widget::Tree],
) -> Node
where
@ -78,13 +78,13 @@ where
let total_spacing = spacing * items.len().saturating_sub(1) as f32;
let max_cross = axis.cross(limits.max());
let compression = limits.compression();
let (main_compress, cross_compress) =
axis.pack(compression.width, compression.height);
let mut fill_main_sum = 0;
let mut some_fill_cross = false;
let (mut cross, cross_compress) = match axis {
Axis::Vertical if width == Length::Shrink => (0.0, true),
Axis::Horizontal if height == Length::Shrink => (0.0, true),
_ => (max_cross, false),
};
let mut cross = if cross_compress { 0.0 } else { max_cross };
let mut available = axis.main(limits.max()) - total_spacing;
@ -95,14 +95,16 @@ where
// We lay out non-fluid elements in the main axis.
// If we need to compress the cross axis, then we skip any of these elements
// that are also fluid in the cross axis.
for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate() {
for (i, (child, tree)) in items.iter_mut().zip(trees.iter_mut()).enumerate()
{
let (fill_main_factor, fill_cross_factor) = {
let size = child.as_widget().size();
axis.pack(size.width.fill_factor(), size.height.fill_factor())
};
if fill_main_factor == 0 && (!cross_compress || fill_cross_factor == 0)
if (main_compress || fill_main_factor == 0)
&& (!cross_compress || fill_cross_factor == 0)
{
let (max_width, max_height) = axis.pack(
available,
@ -113,11 +115,14 @@ where
},
);
let child_limits =
Limits::new(Size::ZERO, Size::new(max_width, max_height));
let child_limits = Limits::with_compression(
Size::ZERO,
Size::new(max_width, max_height),
compression,
);
let layout =
child.as_widget().layout(tree, renderer, &child_limits);
child.as_widget_mut().layout(tree, renderer, &child_limits);
let size = layout.size();
available -= axis.main(size);
@ -141,7 +146,8 @@ where
// We can defer the layout of any elements that have a fixed size in the main axis,
// allowing them to use the cross calculations of the next pass.
if cross_compress && some_fill_cross {
for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate()
for (i, (child, tree)) in
items.iter_mut().zip(trees.iter_mut()).enumerate()
{
let (main_size, cross_size) = {
let size = child.as_widget().size();
@ -149,7 +155,9 @@ where
axis.pack(size.width, size.height)
};
if main_size.fill_factor() == 0 && cross_size.fill_factor() != 0 {
if (main_compress || main_size.fill_factor() == 0)
&& cross_size.fill_factor() != 0
{
if let Length::Fixed(main) = main_size {
available -= main;
continue;
@ -157,11 +165,14 @@ where
let (max_width, max_height) = axis.pack(available, cross);
let child_limits =
Limits::new(Size::ZERO, Size::new(max_width, max_height));
let child_limits = Limits::with_compression(
Size::ZERO,
Size::new(max_width, max_height),
compression,
);
let layout =
child.as_widget().layout(tree, renderer, &child_limits);
child.as_widget_mut().layout(tree, renderer, &child_limits);
let size = layout.size();
available -= axis.main(size);
@ -172,63 +183,59 @@ where
}
}
let remaining = match axis {
Axis::Horizontal => match width {
Length::Shrink => 0.0,
_ => available.max(0.0),
},
Axis::Vertical => match height {
Length::Shrink => 0.0,
_ => available.max(0.0),
},
};
let remaining = available.max(0.0);
// THIRD PASS
// THIRD PASS (conditional)
// We lay out the elements that are fluid in the main axis.
// We use the remaining space to evenly allocate space based on fill factors.
for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate() {
let (fill_main_factor, fill_cross_factor) = {
let size = child.as_widget().size();
if !main_compress {
for (i, (child, tree)) in
items.iter_mut().zip(trees.iter_mut()).enumerate()
{
let (fill_main_factor, fill_cross_factor) = {
let size = child.as_widget().size();
axis.pack(size.width.fill_factor(), size.height.fill_factor())
};
if fill_main_factor != 0 {
let max_main =
remaining * fill_main_factor as f32 / fill_main_sum as f32;
let max_main = if max_main.is_nan() {
f32::INFINITY
} else {
max_main
axis.pack(size.width.fill_factor(), size.height.fill_factor())
};
let min_main = if max_main.is_infinite() {
0.0
} else {
max_main
};
if fill_main_factor != 0 {
let max_main =
remaining * fill_main_factor as f32 / fill_main_sum as f32;
let (min_width, min_height) = axis.pack(min_main, 0.0);
let (max_width, max_height) = axis.pack(
max_main,
if fill_cross_factor == 0 {
max_cross
let max_main = if max_main.is_nan() {
f32::INFINITY
} else {
cross
},
);
max_main
};
let child_limits = Limits::new(
Size::new(min_width, min_height),
Size::new(max_width, max_height),
);
let min_main = if max_main.is_infinite() {
0.0
} else {
max_main
};
let layout =
child.as_widget().layout(tree, renderer, &child_limits);
cross = cross.max(axis.cross(layout.size()));
let (min_width, min_height) = axis.pack(min_main, 0.0);
let (max_width, max_height) = axis.pack(
max_main,
if fill_cross_factor == 0 {
max_cross
} else {
cross
},
);
nodes[i] = layout;
let child_limits = Limits::with_compression(
Size::new(min_width, min_height),
Size::new(max_width, max_height),
compression,
);
let layout =
child.as_widget_mut().layout(tree, renderer, &child_limits);
cross = cross.max(axis.cross(layout.size()));
nodes[i] = layout;
}
}
}
@ -237,7 +244,7 @@ where
// These are elements that must be compressed in their cross axis and have
// a fixed length in the main axis.
if cross_compress && some_fill_cross {
for (i, (child, tree)) in items.iter().zip(trees).enumerate() {
for (i, (child, tree)) in items.iter_mut().zip(trees).enumerate() {
let (main_size, cross_size) = {
let size = child.as_widget().size();
@ -255,7 +262,7 @@ where
Limits::new(Size::ZERO, Size::new(max_width, max_height));
let layout =
child.as_widget().layout(tree, renderer, &child_limits);
child.as_widget_mut().layout(tree, renderer, &child_limits);
let size = layout.size();
cross = cross.max(axis.cross(size));

View file

@ -6,18 +6,34 @@ use crate::{Length, Size};
pub struct Limits {
min: Size,
max: Size,
compression: Size<bool>,
}
impl Limits {
/// No limits
pub const NONE: Limits = Limits {
min: Size::ZERO,
max: Size::INFINITY,
max: Size::INFINITE,
compression: Size::new(false, false),
};
/// Creates new [`Limits`] with the given minimum and maximum [`Size`].
pub const fn new(min: Size, max: Size) -> Limits {
Limits { min, max }
Limits::with_compression(min, max, Size::new(false, false))
}
/// Creates new [`Limits`] with the given minimun and maximum [`Size`], and
/// whether fluid lengths should be compressed to intrinsic dimensions.
pub const fn with_compression(
min: Size,
max: Size,
compress: Size<bool>,
) -> Self {
Limits {
min,
max,
compression: compress,
}
}
/// Returns the minimum [`Size`] of the [`Limits`].
@ -30,16 +46,25 @@ impl Limits {
self.max
}
/// Returns the compression of the [`Limits`].
pub fn compression(&self) -> Size<bool> {
self.compression
}
/// Applies a width constraint to the current [`Limits`].
pub fn width(mut self, width: impl Into<Length>) -> Limits {
match width.into() {
Length::Shrink | Length::Fill | Length::FillPortion(_) => {}
Length::Shrink => {
self.compression.width = true;
}
Length::Fixed(amount) => {
let new_width = amount.min(self.max.width).max(self.min.width);
self.min.width = new_width;
self.max.width = new_width;
self.compression.width = false;
}
Length::Fill | Length::FillPortion(_) => {}
}
self
@ -48,14 +73,18 @@ impl Limits {
/// Applies a height constraint to the current [`Limits`].
pub fn height(mut self, height: impl Into<Length>) -> Limits {
match height.into() {
Length::Shrink | Length::Fill | Length::FillPortion(_) => {}
Length::Shrink => {
self.compression.height = true;
}
Length::Fixed(amount) => {
let new_height =
amount.min(self.max.height).max(self.min.height);
self.min.height = new_height;
self.max.height = new_height;
self.compression.height = false;
}
Length::Fill | Length::FillPortion(_) => {}
}
self
@ -103,7 +132,11 @@ impl Limits {
(self.max().height - size.height).max(0.0),
);
Limits { min, max }
Limits {
min,
max,
compression: self.compression,
}
}
/// Removes the minimum width constraint for the current [`Limits`].
@ -111,6 +144,7 @@ impl Limits {
Limits {
min: Size::ZERO,
max: self.max,
compression: self.compression,
}
}
@ -124,21 +158,27 @@ impl Limits {
intrinsic_size: Size,
) -> Size {
let width = match width.into() {
Length::Fill | Length::FillPortion(_) => self.max.width,
Length::Fill | Length::FillPortion(_)
if !self.compression.width =>
{
self.max.width
}
Length::Fixed(amount) => {
amount.min(self.max.width).max(self.min.width)
}
Length::Shrink => {
intrinsic_size.width.min(self.max.width).max(self.min.width)
}
_ => intrinsic_size.width.min(self.max.width).max(self.min.width),
};
let height = match height.into() {
Length::Fill | Length::FillPortion(_) => self.max.height,
Length::Fill | Length::FillPortion(_)
if !self.compression.height =>
{
self.max.height
}
Length::Fixed(amount) => {
amount.min(self.max.height).max(self.min.height)
}
Length::Shrink => intrinsic_size
_ => intrinsic_size
.height
.min(self.max.height)
.max(self.min.height),

View file

@ -34,8 +34,11 @@ where
}
impl Rectangle<f32> {
/// A rectangle starting at [`Point::ORIGIN`] with infinite width and height.
pub const INFINITE: Self = Self::new(Point::ORIGIN, Size::INFINITY);
/// A rectangle starting at negative infinity and with infinite width and height.
pub const INFINITE: Self = Self::new(
Point::new(f32::NEG_INFINITY, f32::NEG_INFINITY),
Size::INFINITE,
);
/// Creates a new [`Rectangle`] with its top-left corner in the given
/// [`Point`] and with the provided [`Size`].

View file

@ -60,8 +60,8 @@ pub trait Renderer {
/// Fills a [`Quad`] with the provided [`Background`].
fn fill_quad(&mut self, quad: Quad, background: impl Into<Background>);
/// Clears all of the recorded primitives in the [`Renderer`].
fn clear(&mut self);
/// Resets the [`Renderer`] to start drawing in the `new_bounds` from scratch.
fn reset(&mut self, new_bounds: Rectangle);
}
/// A polygon with four sides.

View file

@ -16,7 +16,7 @@ impl Renderer for () {
fn end_transformation(&mut self) {}
fn clear(&mut self) {}
fn reset(&mut self, _new_bounds: Rectangle) {}
fn fill_quad(
&mut self,

View file

@ -24,7 +24,7 @@ impl Size {
pub const UNIT: Size = Size::new(1., 1.);
/// A [`Size`] with infinite width and height.
pub const INFINITY: Size = Size::new(f32::INFINITY, f32::INFINITY);
pub const INFINITE: Size = Size::new(f32::INFINITY, f32::INFINITY);
/// Returns the minimum of each component of this size and another.
pub fn min(self, other: Self) -> Self {

View file

@ -58,7 +58,7 @@ where
/// This [`layout::Node`] is used by the runtime to compute the [`Layout`] of the
/// user interface.
fn layout(
&self,
&mut self,
tree: &mut Tree,
renderer: &Renderer,
limits: &layout::Limits,
@ -102,7 +102,7 @@ where
/// Applies an [`Operation`] to the [`Widget`].
fn operate(
&self,
&mut self,
_state: &mut Tree,
_layout: Layout<'_>,
_renderer: &Renderer,

View file

@ -207,7 +207,7 @@ where
}
fn layout(
&self,
&mut self,
tree: &mut Tree,
renderer: &Renderer,
limits: &layout::Limits,
@ -245,7 +245,7 @@ where
}
fn operate(
&self,
&mut self,
_state: &mut Tree,
layout: Layout<'_>,
_renderer: &Renderer,

View file

@ -35,7 +35,7 @@ pub fn from_rgba(
}
/// An window icon normally used for the titlebar or taskbar.
#[derive(Debug, Clone)]
#[derive(Clone)]
pub struct Icon {
rgba: Vec<u8>,
size: Size<u32>,
@ -48,6 +48,15 @@ impl Icon {
}
}
impl std::fmt::Debug for Icon {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Icon")
.field("rgba", &format!("{} pixels", self.rgba.len() / 4))
.field("size", &self.size)
.finish()
}
}
#[derive(Debug, thiserror::Error)]
/// An error produced when using [`from_rgba`] with invalid arguments.
pub enum Error {