Merge branch 'master' into update-winit

This commit is contained in:
Héctor Ramón Jiménez 2024-01-16 12:02:42 +01:00
commit 534c7dd7b0
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
93 changed files with 1642 additions and 970 deletions

View file

@ -126,7 +126,7 @@ bytemuck = { version = "1.0", features = ["derive"] }
cosmic-text = "0.10"
futures = "0.3"
glam = "0.24"
glyphon = { git = "https://github.com/grovesNL/glyphon.git", rev = "2caa9fc5e5923c1d827d177c3619cab7e9885b85" }
glyphon = "0.4"
guillotiere = "0.6"
half = "2.2"
image = "0.24"

View file

@ -6,7 +6,7 @@ use crate::renderer;
use crate::widget;
use crate::widget::tree::{self, Tree};
use crate::{
Clipboard, Color, Layout, Length, Rectangle, Shell, Vector, Widget,
Clipboard, Color, Layout, Length, Rectangle, Shell, Size, Vector, Widget,
};
use std::any::Any;
@ -296,12 +296,8 @@ where
self.widget.diff(tree);
}
fn width(&self) -> Length {
self.widget.width()
}
fn height(&self) -> Length {
self.widget.height()
fn size(&self) -> Size<Length> {
self.widget.size()
}
fn layout(
@ -466,12 +462,8 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
where
Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.element.widget.width()
}
fn height(&self) -> Length {
self.element.widget.height()
fn size(&self) -> Size<Length> {
self.element.widget.size()
}
fn tag(&self) -> tree::Tag {

View file

@ -7,7 +7,7 @@ pub mod flex;
pub use limits::Limits;
pub use node::Node;
use crate::{Point, Rectangle, Size, Vector};
use crate::{Length, Padding, Point, Rectangle, Size, Vector};
/// The bounds of a [`Node`] and its children, using absolute coordinates.
#[derive(Debug, Clone, Copy)]
@ -71,12 +71,12 @@ pub fn next_to_each_other(
left: impl FnOnce(&Limits) -> Node,
right: impl FnOnce(&Limits) -> Node,
) -> Node {
let mut left_node = left(limits);
let left_node = left(limits);
let left_size = left_node.size();
let right_limits = limits.shrink(Size::new(left_size.width + spacing, 0.0));
let mut right_node = right(&right_limits);
let right_node = right(&right_limits);
let right_size = right_node.size();
let (left_y, right_y) = if left_size.height > right_size.height {
@ -85,14 +85,106 @@ pub fn next_to_each_other(
((right_size.height - left_size.height) / 2.0, 0.0)
};
left_node.move_to(Point::new(0.0, left_y));
right_node.move_to(Point::new(left_size.width + spacing, right_y));
Node::with_children(
Size::new(
left_size.width + spacing + right_size.width,
left_size.height.max(right_size.height),
),
vec![left_node, right_node],
vec![
left_node.move_to(Point::new(0.0, left_y)),
right_node.move_to(Point::new(left_size.width + spacing, right_y)),
],
)
}
/// Computes the resulting [`Node`] that fits the [`Limits`] given
/// some width and height requirements and no intrinsic size.
pub fn atomic(
limits: &Limits,
width: impl Into<Length>,
height: impl Into<Length>,
) -> Node {
let width = width.into();
let height = height.into();
Node::new(limits.resolve(width, height, Size::ZERO))
}
/// Computes the resulting [`Node`] that fits the [`Limits`] given
/// some width and height requirements and a closure that produces
/// the intrinsic [`Size`] inside the given [`Limits`].
pub fn sized(
limits: &Limits,
width: impl Into<Length>,
height: impl Into<Length>,
f: impl FnOnce(&Limits) -> Size,
) -> Node {
let width = width.into();
let height = height.into();
let limits = limits.width(width).height(height);
let intrinsic_size = f(&limits);
Node::new(limits.resolve(width, height, intrinsic_size))
}
/// Computes the resulting [`Node`] that fits the [`Limits`] given
/// some width and height requirements and a closure that produces
/// the content [`Node`] inside the given [`Limits`].
pub fn contained(
limits: &Limits,
width: impl Into<Length>,
height: impl Into<Length>,
f: impl FnOnce(&Limits) -> Node,
) -> Node {
let width = width.into();
let height = height.into();
let limits = limits.width(width).height(height);
let content = f(&limits);
Node::with_children(
limits.resolve(width, height, content.size()),
vec![content],
)
}
/// Computes the [`Node`] that fits the [`Limits`] given some width, height, and
/// [`Padding`] requirements and a closure that produces the content [`Node`]
/// inside the given [`Limits`].
pub fn padded(
limits: &Limits,
width: impl Into<Length>,
height: impl Into<Length>,
padding: impl Into<Padding>,
layout: impl FnOnce(&Limits) -> Node,
) -> Node {
positioned(limits, width, height, padding, layout, |content, _| content)
}
/// Computes a [`padded`] [`Node`] with a positioning step.
pub fn positioned(
limits: &Limits,
width: impl Into<Length>,
height: impl Into<Length>,
padding: impl Into<Padding>,
layout: impl FnOnce(&Limits) -> Node,
position: impl FnOnce(Node, Size) -> Node,
) -> Node {
let width = width.into();
let height = height.into();
let padding = padding.into();
let limits = limits.width(width).height(height);
let content = layout(&limits.shrink(padding));
let padding = padding.fit(content.size(), limits.max());
let size = limits
.shrink(padding)
.resolve(width, height, content.size());
Node::with_children(
size.expand(padding),
vec![position(content.move_to((padding.left, padding.top)), size)],
)
}

View file

@ -20,7 +20,7 @@ use crate::Element;
use crate::layout::{Limits, Node};
use crate::widget;
use crate::{Alignment, Padding, Point, Size};
use crate::{Alignment, Length, Padding, Point, Size};
/// The main axis of a flex layout.
#[derive(Debug)]
@ -47,7 +47,7 @@ impl Axis {
}
}
fn pack(&self, main: f32, cross: f32) -> (f32, f32) {
fn pack<T>(&self, main: T, cross: T) -> (T, T) {
match self {
Axis::Horizontal => (main, cross),
Axis::Vertical => (cross, main),
@ -63,6 +63,8 @@ pub fn resolve<Message, Renderer>(
axis: Axis,
renderer: &Renderer,
limits: &Limits,
width: Length,
height: Length,
padding: Padding,
spacing: f32,
align_items: Alignment,
@ -72,25 +74,36 @@ pub fn resolve<Message, Renderer>(
where
Renderer: crate::Renderer,
{
let limits = limits.pad(padding);
let limits = limits.width(width).height(height).shrink(padding);
let total_spacing = spacing * items.len().saturating_sub(1) as f32;
let max_cross = axis.cross(limits.max());
let mut fill_sum = 0;
let mut cross = axis.cross(limits.min()).max(axis.cross(limits.fill()));
let mut fill_main_sum = 0;
let mut cross = match axis {
Axis::Horizontal => match height {
Length::Shrink => 0.0,
_ => max_cross,
},
Axis::Vertical => match width {
Length::Shrink => 0.0,
_ => max_cross,
},
};
let mut available = axis.main(limits.max()) - total_spacing;
let mut nodes: Vec<Node> = Vec::with_capacity(items.len());
nodes.resize(items.len(), Node::default());
for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate() {
let fill_factor = match axis {
Axis::Horizontal => child.as_widget().width(),
Axis::Vertical => child.as_widget().height(),
}
.fill_factor();
let (fill_main_factor, fill_cross_factor) = {
let size = child.as_widget().size();
if fill_factor == 0 {
axis.pack(size.width.fill_factor(), size.height.fill_factor())
};
if fill_main_factor == 0 {
if fill_cross_factor == 0 {
let (max_width, max_height) = axis.pack(available, max_cross);
let child_limits =
@ -104,31 +117,71 @@ where
cross = cross.max(axis.cross(size));
nodes[i] = layout;
}
} else {
fill_sum += fill_factor;
fill_main_sum += fill_main_factor;
}
}
let remaining = available.max(0.0);
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();
axis.pack(size.width.fill_factor(), size.height.fill_factor())
};
if fill_main_factor == 0 && fill_cross_factor != 0 {
let (max_width, max_height) = axis.pack(available, cross);
let child_limits =
Limits::new(Size::ZERO, Size::new(max_width, max_height));
let layout =
child.as_widget().layout(tree, renderer, &child_limits);
let size = layout.size();
available -= axis.main(size);
cross = cross.max(axis.cross(layout.size()));
nodes[i] = layout;
}
}
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),
},
};
for (i, (child, tree)) in items.iter().zip(trees).enumerate() {
let fill_factor = match axis {
Axis::Horizontal => child.as_widget().width(),
Axis::Vertical => child.as_widget().height(),
}
.fill_factor();
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;
if fill_factor != 0 {
let max_main = remaining * fill_factor as f32 / fill_sum as f32;
let min_main = if max_main.is_infinite() {
0.0
} else {
max_main
};
let (min_width, min_height) =
axis.pack(min_main, axis.cross(limits.min()));
let max_cross = if fill_cross_factor == 0 {
max_cross
} else {
cross
};
let (min_width, min_height) = axis.pack(min_main, 0.0);
let (max_width, max_height) = axis.pack(max_main, max_cross);
let child_limits = Limits::new(
@ -154,18 +207,18 @@ where
let (x, y) = axis.pack(main, pad.1);
node.move_to(Point::new(x, y));
node.move_to_mut(Point::new(x, y));
match axis {
Axis::Horizontal => {
node.align(
node.align_mut(
Alignment::Start,
align_items,
Size::new(0.0, cross),
);
}
Axis::Vertical => {
node.align(
node.align_mut(
align_items,
Alignment::Start,
Size::new(cross, 0.0),
@ -178,8 +231,12 @@ where
main += axis.main(size);
}
let (width, height) = axis.pack(main - pad.0, cross);
let size = limits.resolve(Size::new(width, height));
let (intrinsic_width, intrinsic_height) = axis.pack(main - pad.0, cross);
let size = limits.resolve(
width,
height,
Size::new(intrinsic_width, intrinsic_height),
);
Node::with_children(size.pad(padding), nodes)
Node::with_children(size.expand(padding), nodes)
}

View file

@ -1,12 +1,11 @@
#![allow(clippy::manual_clamp)]
use crate::{Length, Padding, Size};
use crate::{Length, Size};
/// A set of size constraints for layouting.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Limits {
min: Size,
max: Size,
fill: Size,
}
impl Limits {
@ -14,16 +13,11 @@ impl Limits {
pub const NONE: Limits = Limits {
min: Size::ZERO,
max: Size::INFINITY,
fill: Size::INFINITY,
};
/// Creates new [`Limits`] with the given minimum and maximum [`Size`].
pub const fn new(min: Size, max: Size) -> Limits {
Limits {
min,
max,
fill: Size::INFINITY,
}
Limits { min, max }
}
/// Returns the minimum [`Size`] of the [`Limits`].
@ -36,26 +30,15 @@ impl Limits {
self.max
}
/// Returns the fill [`Size`] of the [`Limits`].
pub fn fill(&self) -> Size {
self.fill
}
/// Applies a width constraint to the current [`Limits`].
pub fn width(mut self, width: impl Into<Length>) -> Limits {
match width.into() {
Length::Shrink => {
self.fill.width = self.min.width;
}
Length::Fill | Length::FillPortion(_) => {
self.fill.width = self.fill.width.min(self.max.width);
}
Length::Shrink | Length::Fill | Length::FillPortion(_) => {}
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.fill.width = new_width;
}
}
@ -65,19 +48,13 @@ 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 => {
self.fill.height = self.min.height;
}
Length::Fill | Length::FillPortion(_) => {
self.fill.height = self.fill.height.min(self.max.height);
}
Length::Shrink | Length::Fill | Length::FillPortion(_) => {}
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.fill.height = new_height;
}
}
@ -112,13 +89,10 @@ impl Limits {
self
}
/// Shrinks the current [`Limits`] to account for the given padding.
pub fn pad(&self, padding: Padding) -> Limits {
self.shrink(Size::new(padding.horizontal(), padding.vertical()))
}
/// Shrinks the current [`Limits`] by the given [`Size`].
pub fn shrink(&self, size: Size) -> Limits {
pub fn shrink(&self, size: impl Into<Size>) -> Limits {
let size = size.into();
let min = Size::new(
(self.min().width - size.width).max(0.0),
(self.min().height - size.height).max(0.0),
@ -129,12 +103,7 @@ impl Limits {
(self.max().height - size.height).max(0.0),
);
let fill = Size::new(
(self.fill.width - size.width).max(0.0),
(self.fill.height - size.height).max(0.0),
);
Limits { min, max, fill }
Limits { min, max }
}
/// Removes the minimum width constraint for the current [`Limits`].
@ -142,22 +111,39 @@ impl Limits {
Limits {
min: Size::ZERO,
max: self.max,
fill: self.fill,
}
}
/// Computes the resulting [`Size`] that fits the [`Limits`] given the
/// intrinsic size of some content.
pub fn resolve(&self, intrinsic_size: Size) -> Size {
Size::new(
intrinsic_size
.width
.min(self.max.width)
.max(self.fill.width),
intrinsic_size
/// Computes the resulting [`Size`] that fits the [`Limits`] given
/// some width and height requirements and the intrinsic size of
/// some content.
pub fn resolve(
&self,
width: impl Into<Length>,
height: impl Into<Length>,
intrinsic_size: Size,
) -> Size {
let width = match width.into() {
Length::Fill | Length::FillPortion(_) => 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)
}
};
let height = match height.into() {
Length::Fill | Length::FillPortion(_) => self.max.height,
Length::Fixed(amount) => {
amount.min(self.max.height).max(self.min.height)
}
Length::Shrink => intrinsic_size
.height
.min(self.max.height)
.max(self.fill.height),
)
.max(self.min.height),
};
Size::new(width, height)
}
}

View file

@ -1,4 +1,4 @@
use crate::{Alignment, Point, Rectangle, Size, Vector};
use crate::{Alignment, Padding, Point, Rectangle, Size, Vector};
/// The bounds of an element and its children.
#[derive(Debug, Clone, Default)]
@ -26,6 +26,14 @@ impl Node {
}
}
/// Creates a new [`Node`] that wraps a single child with some [`Padding`].
pub fn container(child: Self, padding: Padding) -> Self {
Self::with_children(
child.bounds.size().expand(padding),
vec![child.move_to(Point::new(padding.left, padding.top))],
)
}
/// Returns the [`Size`] of the [`Node`].
pub fn size(&self) -> Size {
Size::new(self.bounds.width, self.bounds.height)
@ -43,6 +51,17 @@ impl Node {
/// Aligns the [`Node`] in the given space.
pub fn align(
mut self,
horizontal_alignment: Alignment,
vertical_alignment: Alignment,
space: Size,
) -> Self {
self.align_mut(horizontal_alignment, vertical_alignment, space);
self
}
/// Mutable reference version of [`Self::align`].
pub fn align_mut(
&mut self,
horizontal_alignment: Alignment,
vertical_alignment: Alignment,
@ -70,13 +89,23 @@ impl Node {
}
/// Moves the [`Node`] to the given position.
pub fn move_to(&mut self, position: Point) {
pub fn move_to(mut self, position: impl Into<Point>) -> Self {
self.move_to_mut(position);
self
}
/// Mutable reference version of [`Self::move_to`].
pub fn move_to_mut(&mut self, position: impl Into<Point>) {
let position = position.into();
self.bounds.x = position.x;
self.bounds.y = position.y;
}
/// Translates the [`Node`] by the given translation.
pub fn translate(self, translation: Vector) -> Self {
pub fn translate(self, translation: impl Into<Vector>) -> Self {
let translation = translation.into();
Self {
bounds: self.bounds + translation,
..self

View file

@ -36,6 +36,24 @@ impl Length {
Length::Fixed(_) => 0,
}
}
/// Returns `true` iff the [`Length`] is either [`Length::Fill`] or
// [`Length::FillPortion`].
pub fn is_fill(&self) -> bool {
self.fill_factor() != 0
}
/// Returns the "fluid" variant of the [`Length`].
///
/// Specifically:
/// - [`Length::Shrink`] if [`Length::Shrink`] or [`Length::Fixed`].
/// - [`Length::Fill`] otherwise.
pub fn fluid(&self) -> Length {
match self {
Length::Fill | Length::FillPortion(_) => Length::Fill,
Length::Shrink | Length::Fixed(_) => Length::Shrink,
}
}
}
impl From<Pixels> for Length {

View file

@ -154,3 +154,9 @@ impl From<[f32; 4]> for Padding {
}
}
}
impl From<Padding> for Size {
fn from(padding: Padding) -> Self {
Self::new(padding.horizontal(), padding.vertical())
}
}

View file

@ -36,20 +36,26 @@ impl<T: Num> Point<T> {
}
}
impl From<[f32; 2]> for Point {
fn from([x, y]: [f32; 2]) -> Self {
impl<T> From<[T; 2]> for Point<T>
where
T: Num,
{
fn from([x, y]: [T; 2]) -> Self {
Point { x, y }
}
}
impl From<[u16; 2]> for Point<u16> {
fn from([x, y]: [u16; 2]) -> Self {
Point::new(x, y)
impl<T> From<(T, T)> for Point<T>
where
T: Num,
{
fn from((x, y): (T, T)) -> Self {
Self { x, y }
}
}
impl From<Point> for [f32; 2] {
fn from(point: Point) -> [f32; 2] {
impl<T> From<Point<T>> for [T; 2] {
fn from(point: Point<T>) -> [T; 2] {
[point.x, point.y]
}
}

View file

@ -1,4 +1,4 @@
use crate::{Padding, Vector};
use crate::Vector;
/// An amount of space in 2 dimensions.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@ -26,15 +26,7 @@ impl Size {
/// A [`Size`] with infinite width and height.
pub const INFINITY: Size = Size::new(f32::INFINITY, f32::INFINITY);
/// Increments the [`Size`] to account for the given padding.
pub fn pad(&self, padding: Padding) -> Self {
Size {
width: self.width + padding.horizontal(),
height: self.height + padding.vertical(),
}
}
/// Returns the minimum of each component of this size and another
/// Returns the minimum of each component of this size and another.
pub fn min(self, other: Self) -> Self {
Size {
width: self.width.min(other.width),
@ -42,13 +34,23 @@ impl Size {
}
}
/// Returns the maximum of each component of this size and another
/// Returns the maximum of each component of this size and another.
pub fn max(self, other: Self) -> Self {
Size {
width: self.width.max(other.width),
height: self.height.max(other.height),
}
}
/// Expands this [`Size`] by the given amount.
pub fn expand(self, other: impl Into<Size>) -> Self {
let other = other.into();
Size {
width: self.width + other.width,
height: self.height + other.height,
}
}
}
impl From<[f32; 2]> for Size {

View file

@ -15,7 +15,7 @@ use crate::layout::{self, Layout};
use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::{Clipboard, Length, Rectangle, Shell};
use crate::{Clipboard, Length, Rectangle, Shell, Size};
/// A component that displays information and allows interaction.
///
@ -43,11 +43,16 @@ pub trait Widget<Message, Renderer>
where
Renderer: crate::Renderer,
{
/// Returns the width of the [`Widget`].
fn width(&self) -> Length;
/// Returns the [`Size`] of the [`Widget`] in lengths.
fn size(&self) -> Size<Length>;
/// Returns the height of the [`Widget`].
fn height(&self) -> Length;
/// Returns a [`Size`] hint for laying out the [`Widget`].
///
/// This hint may be used by some widget containers to adjust their sizing strategy
/// during construction.
fn size_hint(&self) -> Size<Length> {
self.size()
}
/// Returns the [`layout::Node`] of the [`Widget`].
///

View file

@ -5,7 +5,9 @@ use crate::mouse;
use crate::renderer;
use crate::text::{self, Paragraph};
use crate::widget::tree::{self, Tree};
use crate::{Color, Element, Layout, Length, Pixels, Point, Rectangle, Widget};
use crate::{
Color, Element, Layout, Length, Pixels, Point, Rectangle, Size, Widget,
};
use std::borrow::Cow;
@ -134,12 +136,11 @@ where
tree::State::new(State(Renderer::Paragraph::default()))
}
fn width(&self) -> Length {
self.width
fn size(&self) -> Size<Length> {
Size {
width: self.width,
height: self.height,
}
fn height(&self) -> Length {
self.height
}
fn layout(
@ -205,7 +206,7 @@ pub fn layout<Renderer>(
where
Renderer: text::Renderer,
{
let limits = limits.width(width).height(height);
layout::sized(limits, width, height, |limits| {
let bounds = limits.max();
let size = size.unwrap_or_else(|| renderer.default_size());
@ -224,9 +225,8 @@ where
shaping,
});
let size = limits.resolve(paragraph.min_bounds());
layout::Node::new(size)
paragraph.min_bounds()
})
}
/// Draws text using the same logic as the [`Text`] widget.

View file

@ -58,7 +58,7 @@ pub enum Event {
/// for each file separately.
FileHovered(PathBuf),
/// A file has beend dropped into the window.
/// A file has been dropped into the window.
///
/// When the user drops multiple files at once, this event will be emitted
/// for each file separately.

View file

@ -26,12 +26,11 @@ mod quad {
where
Renderer: renderer::Renderer,
{
fn width(&self) -> Length {
Length::Shrink
fn size(&self) -> Size<Length> {
Size {
width: Length::Shrink,
height: Length::Shrink,
}
fn height(&self) -> Length {
Length::Shrink
}
fn layout(

View file

@ -33,12 +33,11 @@ mod circle {
where
Renderer: renderer::Renderer,
{
fn width(&self) -> Length {
Length::Shrink
fn size(&self) -> Size<Length> {
Size {
width: Length::Shrink,
height: Length::Shrink,
}
fn height(&self) -> Length {
Length::Shrink
}
fn layout(

View file

@ -73,9 +73,8 @@ impl Application for Example {
}
fn view(&self) -> Element<Message> {
let downloads = Column::with_children(
self.downloads.iter().map(Download::view).collect(),
)
let downloads =
Column::with_children(self.downloads.iter().map(Download::view))
.push(
button("Add another download")
.on_press(Message::Add)

View file

@ -12,4 +12,4 @@ iced.features = ["highlighter", "tokio", "debug"]
tokio.workspace = true
tokio.features = ["fs"]
rfd = "0.12"
rfd = "0.13"

View file

@ -82,8 +82,7 @@ impl Application for Events {
self.last
.iter()
.map(|event| text(format!("{event:?}")).size(40))
.map(Element::from)
.collect(),
.map(Element::from),
);
let toggle = checkbox(

View file

@ -146,7 +146,8 @@ impl Application for GameOfLife {
.view()
.map(move |message| Message::Grid(message, version)),
controls,
];
]
.height(Length::Fill);
container(content)
.width(Length::Fill)
@ -178,7 +179,6 @@ fn view_controls<'a>(
slider(1.0..=1000.0, speed as f32, Message::SpeedChanged),
text(format!("x{speed}")).size(16),
]
.width(Length::Fill)
.align_items(Alignment::Center)
.spacing(10);

View file

@ -16,12 +16,11 @@ mod rainbow {
}
impl<Message> Widget<Message, Renderer> for Rainbow {
fn width(&self) -> Length {
Length::Fill
fn size(&self) -> Size<Length> {
Size {
width: Length::Fill,
height: Length::Shrink,
}
fn height(&self) -> Length {
Length::Shrink
}
fn layout(
@ -30,9 +29,9 @@ mod rainbow {
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let size = limits.width(Length::Fill).resolve(Size::ZERO);
let width = limits.max().width;
layout::Node::new(Size::new(size.width, size.width))
layout::Node::new(Size::new(width, width))
}
fn draw(

View file

@ -81,21 +81,14 @@ impl Program for Controls {
);
Row::new()
.width(Length::Fill)
.height(Length::Fill)
.align_items(Alignment::End)
.push(
Column::new()
.width(Length::Fill)
.align_items(Alignment::End)
.push(
Column::new().align_items(Alignment::End).push(
Column::new()
.padding(10)
.spacing(10)
.push(
Text::new("Background color")
.style(Color::WHITE),
)
.push(Text::new("Background color").style(Color::WHITE))
.push(sliders)
.push(
Text::new(format!("{background_color:?}"))

View file

@ -0,0 +1,9 @@
[package]
name = "layout"
version = "0.1.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
publish = false
[dependencies]
iced = { path = "../..", features = ["canvas"] }

367
examples/layout/src/main.rs Normal file
View file

@ -0,0 +1,367 @@
use iced::executor;
use iced::keyboard;
use iced::mouse;
use iced::theme;
use iced::widget::{
button, canvas, checkbox, column, container, horizontal_space, pick_list,
row, scrollable, text, vertical_rule,
};
use iced::{
color, Alignment, Application, Color, Command, Element, Font, Length,
Point, Rectangle, Renderer, Settings, Subscription, Theme,
};
pub fn main() -> iced::Result {
Layout::run(Settings::default())
}
#[derive(Debug)]
struct Layout {
example: Example,
explain: bool,
theme: Theme,
}
#[derive(Debug, Clone)]
enum Message {
Next,
Previous,
ExplainToggled(bool),
ThemeSelected(Theme),
}
impl Application for Layout {
type Message = Message;
type Theme = Theme;
type Executor = executor::Default;
type Flags = ();
fn new(_flags: Self::Flags) -> (Self, Command<Message>) {
(
Self {
example: Example::default(),
explain: false,
theme: Theme::Light,
},
Command::none(),
)
}
fn title(&self) -> String {
format!("{} - Layout - Iced", self.example.title)
}
fn update(&mut self, message: Self::Message) -> Command<Message> {
match message {
Message::Next => {
self.example = self.example.next();
}
Message::Previous => {
self.example = self.example.previous();
}
Message::ExplainToggled(explain) => {
self.explain = explain;
}
Message::ThemeSelected(theme) => {
self.theme = theme;
}
}
Command::none()
}
fn subscription(&self) -> Subscription<Message> {
keyboard::on_key_release(|key_code, _modifiers| match key_code {
keyboard::KeyCode::Left => Some(Message::Previous),
keyboard::KeyCode::Right => Some(Message::Next),
_ => None,
})
}
fn view(&self) -> Element<Message> {
let header = row![
text(self.example.title).size(20).font(Font::MONOSPACE),
horizontal_space(Length::Fill),
checkbox("Explain", self.explain, Message::ExplainToggled),
pick_list(
Theme::ALL,
Some(self.theme.clone()),
Message::ThemeSelected
),
]
.spacing(20)
.align_items(Alignment::Center);
let example = container(if self.explain {
self.example.view().explain(color!(0x0000ff))
} else {
self.example.view()
})
.style(|theme: &Theme| {
let palette = theme.extended_palette();
container::Appearance::default()
.with_border(palette.background.strong.color, 4.0)
})
.padding(4)
.width(Length::Fill)
.height(Length::Fill)
.center_x()
.center_y();
let controls = row([
(!self.example.is_first()).then_some(
button("← Previous")
.padding([5, 10])
.on_press(Message::Previous)
.into(),
),
Some(horizontal_space(Length::Fill).into()),
(!self.example.is_last()).then_some(
button("Next →")
.padding([5, 10])
.on_press(Message::Next)
.into(),
),
]
.into_iter()
.flatten());
column![header, example, controls]
.spacing(10)
.padding(20)
.into()
}
fn theme(&self) -> Theme {
self.theme.clone()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Example {
title: &'static str,
view: fn() -> Element<'static, Message>,
}
impl Example {
const LIST: &'static [Self] = &[
Self {
title: "Centered",
view: centered,
},
Self {
title: "Column",
view: column_,
},
Self {
title: "Row",
view: row_,
},
Self {
title: "Space",
view: space,
},
Self {
title: "Application",
view: application,
},
Self {
title: "Nested Quotes",
view: nested_quotes,
},
];
fn is_first(self) -> bool {
Self::LIST.first() == Some(&self)
}
fn is_last(self) -> bool {
Self::LIST.last() == Some(&self)
}
fn previous(self) -> Self {
let Some(index) =
Self::LIST.iter().position(|&example| example == self)
else {
return self;
};
Self::LIST
.get(index.saturating_sub(1))
.copied()
.unwrap_or(self)
}
fn next(self) -> Self {
let Some(index) =
Self::LIST.iter().position(|&example| example == self)
else {
return self;
};
Self::LIST.get(index + 1).copied().unwrap_or(self)
}
fn view(&self) -> Element<Message> {
(self.view)()
}
}
impl Default for Example {
fn default() -> Self {
Self::LIST[0]
}
}
fn centered<'a>() -> Element<'a, Message> {
container(text("I am centered!").size(50))
.width(Length::Fill)
.height(Length::Fill)
.center_x()
.center_y()
.into()
}
fn column_<'a>() -> Element<'a, Message> {
column![
"A column can be used to",
"lay out widgets vertically.",
square(50),
square(50),
square(50),
"The amount of space between",
"elements can be configured!",
]
.spacing(40)
.into()
}
fn row_<'a>() -> Element<'a, Message> {
row![
"A row works like a column...",
square(50),
square(50),
square(50),
"but lays out widgets horizontally!",
]
.spacing(40)
.into()
}
fn space<'a>() -> Element<'a, Message> {
row!["Left!", horizontal_space(Length::Fill), "Right!"].into()
}
fn application<'a>() -> Element<'a, Message> {
let header = container(
row![
square(40),
horizontal_space(Length::Fill),
"Header!",
horizontal_space(Length::Fill),
square(40),
]
.padding(10)
.align_items(Alignment::Center),
)
.style(|theme: &Theme| {
let palette = theme.extended_palette();
container::Appearance::default()
.with_border(palette.background.strong.color, 1)
});
let sidebar = container(
column!["Sidebar!", square(50), square(50)]
.spacing(40)
.padding(10)
.width(200)
.align_items(Alignment::Center),
)
.style(theme::Container::Box)
.height(Length::Fill)
.center_y();
let content = container(
scrollable(
column![
"Content!",
square(400),
square(200),
square(400),
"The end"
]
.spacing(40)
.align_items(Alignment::Center)
.width(Length::Fill),
)
.height(Length::Fill),
)
.padding(10);
column![header, row![sidebar, content]].into()
}
fn nested_quotes<'a>() -> Element<'a, Message> {
(1..5)
.fold(column![text("Original text")].padding(10), |quotes, i| {
column![
container(
row![vertical_rule(2), quotes].height(Length::Shrink)
)
.style(|theme: &Theme| {
let palette = theme.extended_palette();
container::Appearance::default().with_background(
if palette.is_dark {
Color {
a: 0.01,
..Color::WHITE
}
} else {
Color {
a: 0.08,
..Color::BLACK
}
},
)
}),
text(format!("Reply {i}"))
]
.spacing(10)
.padding(10)
})
.into()
}
fn square<'a>(size: impl Into<Length> + Copy) -> Element<'a, Message> {
struct Square;
impl canvas::Program<Message> for Square {
type State = ();
fn draw(
&self,
_state: &Self::State,
renderer: &Renderer,
theme: &Theme,
bounds: Rectangle,
_cursor: mouse::Cursor,
) -> Vec<canvas::Geometry> {
let mut frame = canvas::Frame::new(renderer, bounds.size());
let palette = theme.extended_palette();
frame.fill_rectangle(
Point::ORIGIN,
bounds.size(),
palette.background.strong.color,
);
vec![frame.into_geometry()]
}
}
canvas(Square).width(size).height(size).into()
}

View file

@ -178,10 +178,7 @@ impl Sandbox for App {
}
});
column(
items
.into_iter()
.map(|item| {
column(items.into_iter().map(|item| {
let button = button("Delete")
.on_press(Message::DeleteItem(item.clone()))
.style(theme::Button::Destructive);
@ -190,23 +187,14 @@ impl Sandbox for App {
text(&item.name)
.style(theme::Text::Color(item.color.into())),
horizontal_space(Length::Fill),
pick_list(
Color::ALL,
Some(item.color),
move |color| {
Message::ItemColorChanged(
item.clone(),
color,
)
}
),
pick_list(Color::ALL, Some(item.color), move |color| {
Message::ItemColorChanged(item.clone(), color)
}),
button
]
.spacing(20)
.into()
})
.collect(),
)
}))
.spacing(10)
});

View file

@ -244,12 +244,11 @@ where
tree::State::new(State::default())
}
fn width(&self) -> Length {
Length::Fixed(self.size)
fn size(&self) -> Size<Length> {
Size {
width: Length::Fixed(self.size),
height: Length::Fixed(self.size),
}
fn height(&self) -> Length {
Length::Fixed(self.size)
}
fn layout(
@ -258,10 +257,7 @@ where
_renderer: &iced::Renderer<Theme>,
limits: &layout::Limits,
) -> layout::Node {
let limits = limits.width(self.size).height(self.size);
let size = limits.resolve(Size::ZERO);
layout::Node::new(size)
layout::atomic(limits, self.size, self.size)
}
fn on_event(

View file

@ -165,12 +165,11 @@ where
tree::State::new(State::default())
}
fn width(&self) -> Length {
self.width
fn size(&self) -> Size<Length> {
Size {
width: self.width,
height: self.height,
}
fn height(&self) -> Length {
self.height
}
fn layout(
@ -179,10 +178,7 @@ where
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let limits = limits.width(self.width).height(self.height);
let size = limits.resolve(Size::ZERO);
layout::Node::new(size)
layout::atomic(limits, self.width, self.height)
}
fn on_event(

View file

@ -96,15 +96,14 @@ impl Application for LoadingSpinners {
container(
column.push(
row(vec![
text("Cycle duration:").into(),
row![
text("Cycle duration:"),
slider(1.0..=1000.0, self.cycle_duration * 100.0, |x| {
Message::CycleDurationChanged(x / 100.0)
})
.width(200.0)
.into(),
text(format!("{:.2}s", self.cycle_duration)).into(),
])
.width(200.0),
text(format!("{:.2}s", self.cycle_duration)),
]
.align_items(iced::Alignment::Center)
.spacing(20.0),
),

View file

@ -282,12 +282,8 @@ mod modal {
tree.diff_children(&[&self.base, &self.modal]);
}
fn width(&self) -> Length {
self.base.as_widget().width()
}
fn height(&self) -> Length {
self.base.as_widget().height()
fn size(&self) -> Size<Length> {
self.base.as_widget().size()
}
fn layout(
@ -421,17 +417,14 @@ mod modal {
.width(Length::Fill)
.height(Length::Fill);
let mut child = self
let child = self
.content
.as_widget()
.layout(self.tree, renderer, &limits);
.layout(self.tree, renderer, &limits)
.align(Alignment::Center, Alignment::Center, limits.max());
child.align(Alignment::Center, Alignment::Center, limits.max());
let mut node = layout::Node::with_children(self.size, vec![child]);
node.move_to(position);
node
layout::Node::with_children(self.size, vec![child])
.move_to(position)
}
fn on_event(

View file

@ -297,7 +297,6 @@ fn view_content<'a>(
text(format!("{}x{}", size.width, size.height)).size(24),
controls,
]
.width(Length::Fill)
.spacing(10)
.align_items(Alignment::Center);

View file

@ -1,4 +1,4 @@
use iced::widget::{column, container, pick_list, scrollable, vertical_space};
use iced::widget::{column, pick_list, scrollable, vertical_space};
use iced::{Alignment, Element, Length, Sandbox, Settings};
pub fn main() -> iced::Result {
@ -52,12 +52,7 @@ impl Sandbox for Example {
.align_items(Alignment::Center)
.spacing(10);
container(scrollable(content))
.width(Length::Fill)
.height(Length::Fill)
.center_x()
.center_y()
.into()
scrollable(content).into()
}
}

View file

@ -147,63 +147,54 @@ impl Application for ScrollableDemo {
text("Scroller width:"),
scroller_width_slider,
]
.spacing(10)
.width(Length::Fill);
.spacing(10);
let scroll_orientation_controls = column(vec![
text("Scrollbar direction:").into(),
let scroll_orientation_controls = column![
text("Scrollbar direction:"),
radio(
"Vertical",
Direction::Vertical,
Some(self.scrollable_direction),
Message::SwitchDirection,
)
.into(),
),
radio(
"Horizontal",
Direction::Horizontal,
Some(self.scrollable_direction),
Message::SwitchDirection,
)
.into(),
),
radio(
"Both!",
Direction::Multi,
Some(self.scrollable_direction),
Message::SwitchDirection,
)
.into(),
])
.spacing(10)
.width(Length::Fill);
),
]
.spacing(10);
let scroll_alignment_controls = column(vec![
text("Scrollable alignment:").into(),
let scroll_alignment_controls = column![
text("Scrollable alignment:"),
radio(
"Start",
scrollable::Alignment::Start,
Some(self.alignment),
Message::AlignmentChanged,
)
.into(),
),
radio(
"End",
scrollable::Alignment::End,
Some(self.alignment),
Message::AlignmentChanged,
)
.into(),
])
.spacing(10)
.width(Length::Fill);
]
.spacing(10);
let scroll_controls = row![
scroll_slider_controls,
scroll_orientation_controls,
scroll_alignment_controls
]
.spacing(20)
.width(Length::Fill);
.spacing(20);
let scroll_to_end_button = || {
button("Scroll to end")
@ -229,11 +220,11 @@ impl Application for ScrollableDemo {
text("End!"),
scroll_to_beginning_button(),
]
.width(Length::Fill)
.align_items(Alignment::Center)
.padding([40, 0, 40, 0])
.spacing(40),
)
.width(Length::Fill)
.height(Length::Fill)
.direction(scrollable::Direction::Vertical(
Properties::new()
@ -259,6 +250,7 @@ impl Application for ScrollableDemo {
.padding([0, 40, 0, 40])
.spacing(40),
)
.width(Length::Fill)
.height(Length::Fill)
.direction(scrollable::Direction::Horizontal(
Properties::new()
@ -301,6 +293,7 @@ impl Application for ScrollableDemo {
.padding([0, 40, 0, 40])
.spacing(40),
)
.width(Length::Fill)
.height(Length::Fill)
.direction({
let properties = Properties::new()
@ -341,20 +334,11 @@ impl Application for ScrollableDemo {
let content: Element<Message> =
column![scroll_controls, scrollable_content, progress_bars]
.width(Length::Fill)
.height(Length::Fill)
.align_items(Alignment::Center)
.spacing(10)
.into();
Element::from(
container(content)
.width(Length::Fill)
.height(Length::Fill)
.padding(40)
.center_x()
.center_y(),
)
container(content).padding(20).center_x().center_y().into()
}
fn theme(&self) -> Self::Theme {

View file

@ -79,12 +79,10 @@ impl Application for SierpinskiEmulator {
row![
text(format!("Iteration: {:?}", self.graph.iteration)),
slider(0..=10000, self.graph.iteration, Message::IterationSet)
.width(Length::Fill)
]
.padding(10)
.spacing(20),
]
.width(Length::Fill)
.align_items(iced::Alignment::Center)
.into()
}

View file

@ -53,13 +53,16 @@ impl Sandbox for Styling {
self.theme = match theme {
ThemeType::Light => Theme::Light,
ThemeType::Dark => Theme::Dark,
ThemeType::Custom => Theme::custom(theme::Palette {
ThemeType::Custom => Theme::custom(
String::from("Custom"),
theme::Palette {
background: Color::from_rgb(1.0, 0.9, 1.0),
text: Color::BLACK,
primary: Color::from_rgb(0.5, 0.5, 0.0),
success: Color::from_rgb(0.0, 1.0, 0.0),
danger: Color::from_rgb(1.0, 0.0, 0.0),
}),
},
),
}
}
Message::InputChanged(value) => self.input_value = value,
@ -104,10 +107,11 @@ impl Sandbox for Styling {
let progress_bar = progress_bar(0.0..=100.0, self.slider_value);
let scrollable = scrollable(
column!["Scroll me!", vertical_space(800), "You did it!"]
.width(Length::Fill),
)
let scrollable = scrollable(column![
"Scroll me!",
vertical_space(800),
"You did it!"
])
.width(Length::Fill)
.height(100);

View file

@ -63,7 +63,6 @@ impl Sandbox for Tiger {
container(apply_color_filter).width(Length::Fill).center_x()
]
.spacing(20)
.width(Length::Fill)
.height(Length::Fill),
)
.width(Length::Fill)

View file

@ -107,9 +107,7 @@ impl Application for App {
fn view<'a>(&'a self) -> Element<'a, Message> {
let subtitle = |title, content: Element<'a, Message>| {
column![text(title).size(14), content]
.width(Length::Fill)
.spacing(5)
column![text(title).size(14), content].spacing(5)
};
let mut add_toast = button("Add Toast");
@ -154,14 +152,11 @@ impl Application for App {
Message::Timeout
)
.step(1.0)
.width(Length::Fill)
]
.spacing(5)
.into()
),
column![add_toast]
.width(Length::Fill)
.align_items(Alignment::End)
column![add_toast].align_items(Alignment::End)
]
.spacing(10)
.max_width(200),
@ -319,12 +314,8 @@ mod toast {
}
impl<'a, Message> Widget<Message, Renderer> for Manager<'a, Message> {
fn width(&self) -> Length {
self.content.as_widget().width()
}
fn height(&self) -> Length {
self.content.as_widget().height()
fn size(&self) -> Size<Length> {
self.content.as_widget().size()
}
fn layout(
@ -514,14 +505,14 @@ mod toast {
position: Point,
_translation: Vector,
) -> layout::Node {
let limits = layout::Limits::new(Size::ZERO, bounds)
.width(Length::Fill)
.height(Length::Fill);
let limits = layout::Limits::new(Size::ZERO, bounds);
layout::flex::resolve(
layout::flex::Axis::Vertical,
renderer,
&limits,
Length::Fill,
Length::Fill,
10.into(),
10.0,
Alignment::End,

View file

@ -254,13 +254,7 @@ impl Application for Todos {
.spacing(20)
.max_width(800);
scrollable(
container(content)
.width(Length::Fill)
.padding(40)
.center_x(),
)
.into()
scrollable(container(content).padding(40).center_x()).into()
}
}
}
@ -472,7 +466,6 @@ fn empty_message(message: &str) -> Element<'_, Message> {
.horizontal_alignment(alignment::Horizontal::Center)
.style(Color::from([0.7, 0.7, 0.7])),
)
.width(Length::Fill)
.height(200)
.center_y()
.into()

View file

@ -509,7 +509,6 @@ impl<'a> Step {
)
})
.map(Element::from)
.collect()
)
.spacing(10)
]
@ -692,11 +691,7 @@ fn ferris<'a>(
}
fn button<'a, Message: Clone>(label: &str) -> Button<'a, Message> {
iced::widget::button(
text(label).horizontal_alignment(alignment::Horizontal::Center),
)
.padding(12)
.width(100)
iced::widget::button(text(label)).padding([12, 24])
}
fn color_slider<'a>(

View file

@ -3,7 +3,7 @@ mod echo;
use iced::alignment::{self, Alignment};
use iced::executor;
use iced::widget::{
button, column, container, row, scrollable, text, text_input, Column,
button, column, container, row, scrollable, text, text_input,
};
use iced::{
Application, Color, Command, Element, Length, Settings, Subscription, Theme,
@ -108,15 +108,9 @@ impl Application for WebSocket {
.into()
} else {
scrollable(
Column::with_children(
self.messages
.iter()
.cloned()
.map(text)
.map(Element::from)
.collect(),
column(
self.messages.iter().cloned().map(text).map(Element::from),
)
.width(Length::Fill)
.spacing(10),
)
.id(MESSAGE_LOG.clone())
@ -131,7 +125,7 @@ impl Application for WebSocket {
let mut button = button(
text("Send")
.height(Length::Fill)
.height(40)
.vertical_alignment(alignment::Vertical::Center),
)
.padding([0, 20]);
@ -149,7 +143,6 @@ impl Application for WebSocket {
};
column![message_log, new_message_input]
.width(Length::Fill)
.height(Length::Fill)
.padding(20)
.spacing(10)

View file

@ -73,6 +73,11 @@ impl<T: Damage> Damage for Primitive<T> {
bounds.expand(1.5)
}
Self::RawText(raw) => {
// TODO: Add `size` field to `raw` to compute more accurate
// damage bounds (?)
raw.clip_bounds.expand(1.5)
}
Self::Quad { bounds, .. }
| Self::Image { bounds, .. }
| Self::Svg { bounds, .. } => bounds.expand(1.0),

View file

@ -57,6 +57,8 @@ pub enum Primitive<T> {
/// The clip bounds of the editor.
clip_bounds: Rectangle,
},
/// A raw `cosmic-text` primitive
RawText(crate::text::Raw),
/// A quad primitive
Quad {
/// The bounds of the quad

View file

@ -9,14 +9,13 @@ pub use paragraph::Paragraph;
pub use cosmic_text;
use crate::color;
use crate::core::font::{self, Font};
use crate::core::text::Shaping;
use crate::core::{Color, Size};
use crate::core::{Color, Point, Rectangle, Size};
use once_cell::sync::OnceCell;
use std::borrow::Cow;
use std::sync::{Arc, RwLock};
use std::sync::{Arc, RwLock, Weak};
/// Returns the global [`FontSystem`].
pub fn font_system() -> &'static RwLock<FontSystem> {
@ -68,6 +67,29 @@ impl FontSystem {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Version(u32);
/// A weak reference to a [`cosmic-text::Buffer`] that can be drawn.
#[derive(Debug, Clone)]
pub struct Raw {
/// A weak reference to a [`cosmic_text::Buffer`].
pub buffer: Weak<cosmic_text::Buffer>,
/// The position of the text.
pub position: Point,
/// The color of the text.
pub color: Color,
/// The clip bounds of the text.
pub clip_bounds: Rectangle,
}
impl PartialEq for Raw {
fn eq(&self, _other: &Self) -> bool {
// TODO: There is no proper way to compare raw buffers
// For now, no two instances of `Raw` text will be equal.
// This should be fine, but could trigger unnecessary redraws
// in the future.
false
}
}
/// Measures the dimensions of the given [`cosmic_text::Buffer`].
pub fn measure(buffer: &cosmic_text::Buffer) -> Size {
let (width, total_lines) = buffer
@ -150,12 +172,7 @@ pub fn to_shaping(shaping: Shaping) -> cosmic_text::Shaping {
/// Converts some [`Color`] to a [`cosmic_text::Color`].
pub fn to_color(color: Color) -> cosmic_text::Color {
let [r, g, b, a] = color::pack(color).components();
let [r, g, b, a] = color.into_rgba8();
cosmic_text::Color::rgba(
(r * 255.0) as u8,
(g * 255.0) as u8,
(b * 255.0) as u8,
(a * 255.0) as u8,
)
cosmic_text::Color::rgba(r, g, b, a)
}

View file

@ -187,38 +187,43 @@ impl core::text::Paragraph for Paragraph {
}
fn grapheme_position(&self, line: usize, index: usize) -> Option<Point> {
use unicode_segmentation::UnicodeSegmentation;
let run = self.internal().buffer.layout_runs().nth(line)?;
// index represents a grapheme, not a glyph
// Let's find the first glyph for the given grapheme cluster
let mut last_start = None;
let mut last_grapheme_count = 0;
let mut graphemes_seen = 0;
let glyph = run
.glyphs
.iter()
.find(|glyph| {
if graphemes_seen == index {
return true;
}
if Some(glyph.start) != last_start {
last_grapheme_count = run.text[glyph.start..glyph.end]
.graphemes(false)
.count();
last_start = Some(glyph.start);
graphemes_seen += 1;
graphemes_seen += last_grapheme_count;
}
false
graphemes_seen >= index
})
.or_else(|| run.glyphs.last())?;
let advance_last = if index == run.glyphs.len() {
glyph.w
} else {
let advance = if index == 0 {
0.0
} else {
glyph.w
* (1.0
- graphemes_seen.saturating_sub(index) as f32
/ last_grapheme_count.max(1) as f32)
};
Some(Point::new(
glyph.x + glyph.x_offset * glyph.font_size + advance_last,
glyph.x + glyph.x_offset * glyph.font_size + advance,
glyph.y - glyph.y_offset * glyph.font_size,
))
}

View file

@ -1,100 +0,0 @@
use crate::core::text;
use crate::core::{Font, Point, Size};
use crate::graphics::backend;
use std::borrow::Cow;
#[allow(clippy::large_enum_variant)]
pub enum Backend {
TinySkia(iced_tiny_skia::Backend),
#[cfg(feature = "wgpu")]
Wgpu(iced_wgpu::Backend),
}
macro_rules! delegate {
($backend:expr, $name:ident, $body:expr) => {
match $backend {
Self::TinySkia($name) => $body,
#[cfg(feature = "wgpu")]
Self::Wgpu($name) => $body,
}
};
}
impl backend::Text for Backend {
const ICON_FONT: Font = Font::with_name("Iced-Icons");
const CHECKMARK_ICON: char = '\u{f00c}';
const ARROW_DOWN_ICON: char = '\u{e800}';
fn default_font(&self) -> Font {
delegate!(self, backend, backend.default_font())
}
fn default_size(&self) -> f32 {
delegate!(self, backend, backend.default_size())
}
fn measure(
&self,
contents: &str,
size: f32,
line_height: text::LineHeight,
font: Font,
bounds: Size,
shaping: text::Shaping,
) -> Size {
delegate!(
self,
backend,
backend.measure(contents, size, line_height, font, bounds, shaping)
)
}
fn hit_test(
&self,
contents: &str,
size: f32,
line_height: text::LineHeight,
font: Font,
bounds: Size,
shaping: text::Shaping,
position: Point,
nearest_only: bool,
) -> Option<text::Hit> {
delegate!(
self,
backend,
backend.hit_test(
contents,
size,
line_height,
font,
bounds,
shaping,
position,
nearest_only,
)
)
}
fn load_font(&mut self, font: Cow<'static, [u8]>) {
delegate!(self, backend, backend.load_font(font));
}
}
#[cfg(feature = "image")]
impl backend::Image for Backend {
fn dimensions(&self, handle: &crate::core::image::Handle) -> Size<u32> {
delegate!(self, backend, backend.dimensions(handle))
}
}
#[cfg(feature = "svg")]
impl backend::Svg for Backend {
fn viewport_dimensions(
&self,
handle: &crate::core::svg::Handle,
) -> Size<u32> {
delegate!(self, backend, backend.viewport_dimensions(handle))
}
}

View file

@ -65,11 +65,33 @@ pub fn fetch_size<Message>(
Command::single(command::Action::Window(Action::FetchSize(id, Box::new(f))))
}
/// Fetches if the window is maximized.
pub fn fetch_maximized<Message>(
id: Id,
f: impl FnOnce(bool) -> Message + 'static,
) -> Command<Message> {
Command::single(command::Action::Window(Action::FetchMaximized(
id,
Box::new(f),
)))
}
/// Maximizes the window.
pub fn maximize<Message>(id: Id, maximized: bool) -> Command<Message> {
Command::single(command::Action::Window(Action::Maximize(id, maximized)))
}
/// Fetches if the window is minimized.
pub fn fetch_minimized<Message>(
id: Id,
f: impl FnOnce(Option<bool>) -> Message + 'static,
) -> Command<Message> {
Command::single(command::Action::Window(Action::FetchMinimized(
id,
Box::new(f),
)))
}
/// Minimizes the window.
pub fn minimize<Message>(id: Id, minimized: bool) -> Command<Message> {
Command::single(command::Action::Window(Action::Minimize(id, minimized)))

View file

@ -21,8 +21,19 @@ pub enum Action<T> {
Resize(Id, Size),
/// Fetch the current logical dimensions of the window.
FetchSize(Id, Box<dyn FnOnce(Size) -> T + 'static>),
/// Fetch if the current window is maximized or not.
///
/// ## Platform-specific
/// - **iOS / Android / Web:** Unsupported.
FetchMaximized(Id, Box<dyn FnOnce(bool) -> T + 'static>),
/// Set the window to maximized or back
Maximize(Id, bool),
/// Fetch if the current window is minimized or not.
///
/// ## Platform-specific
/// - **Wayland:** Always `None`.
/// - **iOS / Android / Web:** Unsupported.
FetchMinimized(Id, Box<dyn FnOnce(Option<bool>) -> T + 'static>),
/// Set the window to minimized or back
Minimize(Id, bool),
/// Move the window to the given logical coordinates.
@ -106,7 +117,13 @@ impl<T> Action<T> {
Self::FetchSize(id, o) => {
Action::FetchSize(id, Box::new(move |s| f(o(s))))
}
Self::FetchMaximized(id, o) => {
Action::FetchMaximized(id, Box::new(move |s| f(o(s))))
}
Self::Maximize(id, maximized) => Action::Maximize(id, maximized),
Self::FetchMinimized(id, o) => {
Action::FetchMinimized(id, Box::new(move |s| f(o(s))))
}
Self::Minimize(id, minimized) => Action::Minimize(id, minimized),
Self::Move(id, position) => Action::Move(id, position),
Self::ChangeMode(id, mode) => Action::ChangeMode(id, mode),
@ -144,9 +161,15 @@ impl<T> fmt::Debug for Action<T> {
write!(f, "Action::Resize({id:?}, {size:?})")
}
Self::FetchSize(id, _) => write!(f, "Action::FetchSize({id:?})"),
Self::FetchMaximized(id, _) => {
write!(f, "Action::FetchMaximized({id:?})")
}
Self::Maximize(id, maximized) => {
write!(f, "Action::Maximize({id:?}, {maximized})")
}
Self::FetchMinimized(id, _) => {
write!(f, "Action::FetchMinimized({id:?})")
}
Self::Minimize(id, minimized) => {
write!(f, "Action::Minimize({id:?}, {minimized}")
}

View file

@ -1,4 +1,5 @@
//! Listen and react to time.
pub use iced_core::time::{Duration, Instant};
#[allow(unused_imports)]
pub use iced_futures::backend::default::time::*;

View file

@ -1,5 +1,5 @@
//! Change the appearance of a container.
use iced_core::{Background, BorderRadius, Color};
use crate::core::{Background, BorderRadius, Color, Pixels};
/// The appearance of a container.
#[derive(Debug, Clone, Copy)]
@ -16,6 +16,30 @@ pub struct Appearance {
pub border_color: Color,
}
impl Appearance {
/// Derives a new [`Appearance`] with a border of the given [`Color`] and
/// `width`.
pub fn with_border(
self,
color: impl Into<Color>,
width: impl Into<Pixels>,
) -> Self {
Self {
border_color: color.into(),
border_width: width.into().0,
..self
}
}
/// Derives a new [`Appearance`] with the given [`Background`].
pub fn with_background(self, background: impl Into<Background>) -> Self {
Self {
background: Some(background.into()),
..self
}
}
}
impl std::default::Default for Appearance {
fn default() -> Self {
Self {

View file

@ -23,6 +23,7 @@ use crate::toggler;
use iced_core::{Background, Color, Vector};
use std::fmt;
use std::rc::Rc;
/// A built-in theme.
@ -38,18 +39,22 @@ pub enum Theme {
}
impl Theme {
/// A list with all the defined themes.
pub const ALL: &'static [Self] = &[Self::Light, Self::Dark];
/// Creates a new custom [`Theme`] from the given [`Palette`].
pub fn custom(palette: Palette) -> Self {
Self::custom_with_fn(palette, palette::Extended::generate)
pub fn custom(name: String, palette: Palette) -> Self {
Self::custom_with_fn(name, palette, palette::Extended::generate)
}
/// Creates a new custom [`Theme`] from the given [`Palette`], with
/// a custom generator of a [`palette::Extended`].
pub fn custom_with_fn(
name: String,
palette: Palette,
generate: impl FnOnce(Palette) -> palette::Extended,
) -> Self {
Self::Custom(Box::new(Custom::with_fn(palette, generate)))
Self::Custom(Box::new(Custom::with_fn(name, palette, generate)))
}
/// Returns the [`Palette`] of the [`Theme`].
@ -71,32 +76,51 @@ impl Theme {
}
}
impl fmt::Display for Theme {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Light => write!(f, "Light"),
Self::Dark => write!(f, "Dark"),
Self::Custom(custom) => custom.fmt(f),
}
}
}
/// A [`Theme`] with a customized [`Palette`].
#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(Debug, Clone, PartialEq)]
pub struct Custom {
name: String,
palette: Palette,
extended: palette::Extended,
}
impl Custom {
/// Creates a [`Custom`] theme from the given [`Palette`].
pub fn new(palette: Palette) -> Self {
Self::with_fn(palette, palette::Extended::generate)
pub fn new(name: String, palette: Palette) -> Self {
Self::with_fn(name, palette, palette::Extended::generate)
}
/// Creates a [`Custom`] theme from the given [`Palette`] with
/// a custom generator of a [`palette::Extended`].
pub fn with_fn(
name: String,
palette: Palette,
generate: impl FnOnce(Palette) -> palette::Extended,
) -> Self {
Self {
name,
palette,
extended: generate(palette),
}
}
}
impl fmt::Display for Custom {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name)
}
}
/// The style of an application.
#[derive(Default)]
pub enum Application {
@ -383,6 +407,12 @@ pub enum Container {
Custom(Box<dyn container::StyleSheet<Style = Theme>>),
}
impl From<container::Appearance> for Container {
fn from(appearance: container::Appearance) -> Self {
Self::Custom(Box::new(move |_: &_| appearance))
}
}
impl<T: Fn(&Theme) -> container::Appearance + 'static> From<T> for Container {
fn from(f: T) -> Self {
Self::Custom(Box::new(f))

View file

@ -82,6 +82,8 @@ pub struct Extended {
pub success: Success,
/// The set of danger colors.
pub danger: Danger,
/// Whether the palette is dark or not.
pub is_dark: bool,
}
/// The built-in light variant of an [`Extended`] palette.
@ -113,6 +115,7 @@ impl Extended {
palette.background,
palette.text,
),
is_dark: is_dark(palette.background),
}
}
}

View file

@ -1,5 +1,6 @@
use crate::core::{Background, Color, Gradient, Rectangle, Vector};
use crate::graphics::backend;
use crate::graphics::text;
use crate::graphics::Viewport;
use crate::primitive::{self, Primitive};
@ -444,6 +445,35 @@ impl Backend {
clip_mask,
);
}
Primitive::RawText(text::Raw {
buffer,
position,
color,
clip_bounds: text_clip_bounds,
}) => {
let Some(buffer) = buffer.upgrade() else {
return;
};
let physical_bounds =
(*text_clip_bounds + translation) * scale_factor;
if !clip_bounds.intersects(&physical_bounds) {
return;
}
let clip_mask = (!physical_bounds.is_within(&clip_bounds))
.then_some(clip_mask as &_);
self.text_pipeline.draw_raw(
&buffer,
*position + translation,
*color,
scale_factor,
pixels,
clip_mask,
);
}
#[cfg(feature = "image")]
Primitive::Image {
handle,

View file

@ -1,7 +1,6 @@
use crate::core::alignment;
use crate::core::text::{LineHeight, Shaping};
use crate::core::{Color, Font, Pixels, Point, Rectangle};
use crate::graphics::color;
use crate::core::{Color, Font, Pixels, Point, Rectangle, Size};
use crate::graphics::text::cache::{self, Cache};
use crate::graphics::text::editor;
use crate::graphics::text::font_system;
@ -149,6 +148,33 @@ impl Pipeline {
);
}
pub fn draw_raw(
&mut self,
buffer: &cosmic_text::Buffer,
position: Point,
color: Color,
scale_factor: f32,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: Option<&tiny_skia::Mask>,
) {
let mut font_system = font_system().write().expect("Write font system");
let (width, height) = buffer.size();
draw(
font_system.raw(),
&mut self.glyph_cache,
buffer,
Rectangle::new(position, Size::new(width, height)),
color,
alignment::Horizontal::Left,
alignment::Vertical::Top,
scale_factor,
pixels,
clip_mask,
);
}
pub fn trim_cache(&mut self) {
self.cache.get_mut().trim();
self.glyph_cache.trim();
@ -217,18 +243,7 @@ fn draw(
fn from_color(color: cosmic_text::Color) -> Color {
let [r, g, b, a] = color.as_rgba();
if color::GAMMA_CORRECTION {
// `cosmic_text::Color` is linear RGB in this case, so we
// need to convert back to sRGB
Color::from_linear_rgba(
r as f32 / 255.0,
g as f32 / 255.0,
b as f32 / 255.0,
a as f32 / 255.0,
)
} else {
Color::from_rgba8(r, g, b, a as f32 / 255.0)
}
}
#[derive(Debug, Clone, Default)]

View file

@ -177,6 +177,21 @@ impl<'a> Layer<'a> {
clip_bounds: *clip_bounds + translation,
}));
}
graphics::Primitive::RawText(graphics::text::Raw {
buffer,
position,
color,
clip_bounds,
}) => {
let layer = &mut layers[current_layer];
layer.text.push(Text::Raw(graphics::text::Raw {
buffer: buffer.clone(),
position: *position + translation,
color: *color,
clip_bounds: *clip_bounds + translation,
}));
}
Primitive::Quad {
bounds,
background,

View file

@ -1,6 +1,7 @@
use crate::core::alignment;
use crate::core::text;
use crate::core::{Color, Font, Pixels, Point, Rectangle};
use crate::graphics;
use crate::graphics::text::editor;
use crate::graphics::text::paragraph;
@ -23,8 +24,10 @@ pub enum Text<'a> {
color: Color,
clip_bounds: Rectangle,
},
/// A cached text.
/// Some cached text.
Cached(Cached<'a>),
/// Some raw text.
Raw(graphics::text::Raw),
}
#[derive(Debug, Clone)]

View file

@ -82,7 +82,7 @@ impl<Theme> Renderer for crate::Renderer<Theme> {
/// Stores custom, user-provided pipelines.
#[derive(Default, Debug)]
pub struct Storage {
pipelines: HashMap<TypeId, Box<dyn Any>>,
pipelines: HashMap<TypeId, Box<dyn Any + Send>>,
}
impl Storage {
@ -92,7 +92,7 @@ impl Storage {
}
/// Inserts the pipeline `T` in to [`Storage`].
pub fn store<T: 'static>(&mut self, pipeline: T) {
pub fn store<T: 'static + Send>(&mut self, pipeline: T) {
let _ = self.pipelines.insert(TypeId::of::<T>(), Box::new(pipeline));
}

View file

@ -7,6 +7,7 @@ use crate::layer::Text;
use std::borrow::Cow;
use std::cell::RefCell;
use std::sync::Arc;
#[allow(missing_debug_implementations)]
pub struct Pipeline {
@ -76,6 +77,7 @@ impl Pipeline {
Paragraph(Paragraph),
Editor(Editor),
Cache(cache::KeyHash),
Raw(Arc<glyphon::Buffer>),
}
let allocations: Vec<_> = sections
@ -107,6 +109,7 @@ impl Pipeline {
Some(Allocation::Cache(key))
}
Text::Raw(text) => text.buffer.upgrade().map(Allocation::Raw),
})
.collect();
@ -185,6 +188,25 @@ impl Pipeline {
text.clip_bounds,
)
}
Text::Raw(text) => {
let Some(Allocation::Raw(buffer)) = allocation else {
return None;
};
let (width, height) = buffer.size();
(
buffer.as_ref(),
Rectangle::new(
text.position,
Size::new(width, height),
),
alignment::Horizontal::Left,
alignment::Vertical::Top,
text.color,
text.clip_bounds,
)
}
};
let bounds = bounds * scale_factor;

View file

@ -10,8 +10,8 @@ use crate::core::touch;
use crate::core::widget::tree::{self, Tree};
use crate::core::widget::Operation;
use crate::core::{
Background, Clipboard, Color, Element, Layout, Length, Padding, Point,
Rectangle, Shell, Vector, Widget,
Background, Clipboard, Color, Element, Layout, Length, Padding, Rectangle,
Shell, Size, Vector, Widget,
};
pub use iced_style::button::{Appearance, StyleSheet};
@ -71,11 +71,14 @@ where
{
/// Creates a new [`Button`] with the given content.
pub fn new(content: impl Into<Element<'a, Message, Renderer>>) -> Self {
let content = content.into();
let size = content.as_widget().size_hint();
Button {
content: content.into(),
content,
on_press: None,
width: Length::Shrink,
height: Length::Shrink,
width: size.width.fluid(),
height: size.height.fluid(),
padding: Padding::new(5.0),
style: <Renderer::Theme as StyleSheet>::Style::default(),
}
@ -149,12 +152,11 @@ where
tree.diff_children(std::slice::from_ref(&self.content));
}
fn width(&self) -> Length {
self.width
fn size(&self) -> Size<Length> {
Size {
width: self.width,
height: self.height,
}
fn height(&self) -> Length {
self.height
}
fn layout(
@ -431,15 +433,7 @@ pub fn layout(
padding: Padding,
layout_content: impl FnOnce(&layout::Limits) -> layout::Node,
) -> layout::Node {
let limits = limits.width(width).height(height);
let mut content = layout_content(&limits.pad(padding));
let padding = padding.fit(content.size(), limits.max());
let size = limits.pad(padding).resolve(content.size()).pad(padding);
content.move_to(Point::new(padding.left, padding.top));
layout::Node::with_children(size, vec![content])
layout::padded(limits, width, height, padding, layout_content)
}
/// Returns the [`mouse::Interaction`] of a [`Button`].

View file

@ -14,8 +14,9 @@ use crate::core::layout::{self, Layout};
use crate::core::mouse;
use crate::core::renderer;
use crate::core::widget::tree::{self, Tree};
use crate::core::{Clipboard, Element, Shell, Widget};
use crate::core::{Length, Rectangle, Size, Vector};
use crate::core::{
Clipboard, Element, Length, Rectangle, Shell, Size, Vector, Widget,
};
use crate::graphics::geometry;
use std::marker::PhantomData;
@ -119,12 +120,11 @@ where
tree::State::new(P::State::default())
}
fn width(&self) -> Length {
self.width
fn size(&self) -> Size<Length> {
Size {
width: self.width,
height: self.height,
}
fn height(&self) -> Length {
self.height
}
fn layout(
@ -133,10 +133,7 @@ where
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let limits = limits.width(self.width).height(self.height);
let size = limits.resolve(Size::ZERO);
layout::Node::new(size)
layout::atomic(limits, self.width, self.height)
}
fn on_event(

View file

@ -174,12 +174,11 @@ where
tree::State::new(widget::text::State::<Renderer::Paragraph>::default())
}
fn width(&self) -> Length {
self.width
fn size(&self) -> Size<Length> {
Size {
width: self.width,
height: Length::Shrink,
}
fn height(&self) -> Length {
Length::Shrink
}
fn layout(

View file

@ -7,7 +7,7 @@ use crate::core::renderer;
use crate::core::widget::{Operation, Tree};
use crate::core::{
Alignment, Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle,
Shell, Widget,
Shell, Size, Widget,
};
/// A container that distributes its contents vertically.
@ -22,16 +22,12 @@ pub struct Column<'a, Message, Renderer = crate::Renderer> {
children: Vec<Element<'a, Message, Renderer>>,
}
impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
impl<'a, Message, Renderer> Column<'a, Message, Renderer>
where
Renderer: crate::core::Renderer,
{
/// Creates an empty [`Column`].
pub fn new() -> Self {
Self::with_children(Vec::new())
}
/// Creates a [`Column`] with the given elements.
pub fn with_children(
children: Vec<Element<'a, Message, Renderer>>,
) -> Self {
Column {
spacing: 0.0,
padding: Padding::ZERO,
@ -39,10 +35,17 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
height: Length::Shrink,
max_width: f32::INFINITY,
align_items: Alignment::Start,
children,
children: Vec::new(),
}
}
/// Creates a [`Column`] with the given elements.
pub fn with_children(
children: impl IntoIterator<Item = Element<'a, Message, Renderer>>,
) -> Self {
children.into_iter().fold(Self::new(), Self::push)
}
/// Sets the vertical spacing _between_ elements.
///
/// Custom margins per element do not exist in iced. You should use this
@ -88,12 +91,26 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
mut self,
child: impl Into<Element<'a, Message, Renderer>>,
) -> Self {
self.children.push(child.into());
let child = child.into();
let size = child.as_widget().size_hint();
if size.width.is_fill() {
self.width = Length::Fill;
}
if size.height.is_fill() {
self.height = Length::Fill;
}
self.children.push(child);
self
}
}
impl<'a, Message, Renderer> Default for Column<'a, Message, Renderer> {
impl<'a, Message, Renderer> Default for Column<'a, Message, Renderer>
where
Renderer: crate::core::Renderer,
{
fn default() -> Self {
Self::new()
}
@ -112,12 +129,11 @@ where
tree.diff_children(&self.children);
}
fn width(&self) -> Length {
self.width
fn size(&self) -> Size<Length> {
Size {
width: self.width,
height: self.height,
}
fn height(&self) -> Length {
self.height
}
fn layout(
@ -126,15 +142,14 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let limits = limits
.max_width(self.max_width)
.width(self.width)
.height(self.height);
let limits = limits.max_width(self.max_width);
layout::flex::resolve(
layout::flex::Axis::Vertical,
renderer,
&limits,
self.width,
self.height,
self.padding,
self.spacing,
self.align_items,

View file

@ -8,7 +8,9 @@ use crate::core::renderer;
use crate::core::text;
use crate::core::time::Instant;
use crate::core::widget::{self, Widget};
use crate::core::{Clipboard, Element, Length, Padding, Rectangle, Shell};
use crate::core::{
Clipboard, Element, Length, Padding, Rectangle, Shell, Size,
};
use crate::overlay::menu;
use crate::text::LineHeight;
use crate::{container, scrollable, text_input, TextInput};
@ -297,12 +299,8 @@ where
+ scrollable::StyleSheet
+ menu::StyleSheet,
{
fn width(&self) -> Length {
Widget::<TextInputEvent, Renderer>::width(&self.text_input)
}
fn height(&self) -> Length {
Widget::<TextInputEvent, Renderer>::height(&self.text_input)
fn size(&self) -> Size<Length> {
Widget::<TextInputEvent, Renderer>::size(&self.text_input)
}
fn layout(

View file

@ -46,17 +46,20 @@ where
where
T: Into<Element<'a, Message, Renderer>>,
{
let content = content.into();
let size = content.as_widget().size_hint();
Container {
id: None,
padding: Padding::ZERO,
width: Length::Shrink,
height: Length::Shrink,
width: size.width.fluid(),
height: size.height.fluid(),
max_width: f32::INFINITY,
max_height: f32::INFINITY,
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
style: Default::default(),
content: content.into(),
content,
}
}
@ -152,12 +155,11 @@ where
self.content.as_widget().diff(tree);
}
fn width(&self) -> Length {
self.width
fn size(&self) -> Size<Length> {
Size {
width: self.width,
height: self.height,
}
fn height(&self) -> Length {
self.height
}
fn layout(
@ -311,25 +313,20 @@ pub fn layout(
vertical_alignment: alignment::Vertical,
layout_content: impl FnOnce(&layout::Limits) -> layout::Node,
) -> layout::Node {
let limits = limits
.loose()
.max_width(max_width)
.max_height(max_height)
.width(width)
.height(height);
let mut content = layout_content(&limits.pad(padding).loose());
let padding = padding.fit(content.size(), limits.max());
let size = limits.pad(padding).resolve(content.size());
content.move_to(Point::new(padding.left, padding.top));
layout::positioned(
&limits.max_width(max_width).max_height(max_height),
width,
height,
padding,
|limits| layout_content(&limits.loose()),
|content, size| {
content.align(
Alignment::from(horizontal_alignment),
Alignment::from(vertical_alignment),
size,
);
layout::Node::with_children(size.pad(padding), vec![content])
)
},
)
}
/// Draws the background of a [`Container`] given its [`Appearance`] and its `bounds`.

View file

@ -34,7 +34,7 @@ macro_rules! column {
$crate::Column::new()
);
($($x:expr),+ $(,)?) => (
$crate::Column::with_children(vec![$($crate::core::Element::from($x)),+])
$crate::Column::with_children([$($crate::core::Element::from($x)),+])
);
}
@ -47,7 +47,7 @@ macro_rules! row {
$crate::Row::new()
);
($($x:expr),+ $(,)?) => (
$crate::Row::with_children(vec![$($crate::core::Element::from($x)),+])
$crate::Row::with_children([$($crate::core::Element::from($x)),+])
);
}
@ -65,9 +65,12 @@ where
}
/// Creates a new [`Column`] with the given children.
pub fn column<Message, Renderer>(
children: Vec<Element<'_, Message, Renderer>>,
) -> Column<'_, Message, Renderer> {
pub fn column<'a, Message, Renderer>(
children: impl IntoIterator<Item = Element<'a, Message, Renderer>>,
) -> Column<'a, Message, Renderer>
where
Renderer: core::Renderer,
{
Column::with_children(children)
}
@ -77,6 +80,7 @@ pub fn keyed_column<'a, Key, Message, Renderer>(
) -> keyed::Column<'a, Key, Message, Renderer>
where
Key: Copy + PartialEq,
Renderer: core::Renderer,
{
keyed::Column::with_children(children)
}
@ -84,9 +88,12 @@ where
/// Creates a new [`Row`] with the given children.
///
/// [`Row`]: crate::Row
pub fn row<Message, Renderer>(
children: Vec<Element<'_, Message, Renderer>>,
) -> Row<'_, Message, Renderer> {
pub fn row<'a, Message, Renderer>(
children: impl IntoIterator<Item = Element<'a, Message, Renderer>>,
) -> Row<'a, Message, Renderer>
where
Renderer: core::Renderer,
{
Row::with_children(children)
}
@ -264,7 +271,7 @@ pub fn pick_list<'a, Message, Renderer, T>(
on_selected: impl Fn(T) -> Message + 'a,
) -> PickList<'a, T, Message, Renderer>
where
T: ToString + Eq + 'static,
T: ToString + PartialEq + 'static,
[T]: ToOwned<Owned = Vec<T>>,
Renderer: core::text::Renderer,
Renderer::Theme: pick_list::StyleSheet

View file

@ -99,7 +99,7 @@ where
};
// The size to be available to the widget prior to `Shrink`ing
let raw_size = limits.width(width).height(height).resolve(image_size);
let raw_size = limits.resolve(width, height, image_size);
// The uncropped size of the image when fit to the bounds above
let full_size = content_fit.fit(image_size, raw_size);
@ -164,12 +164,11 @@ where
Renderer: image::Renderer<Handle = Handle>,
Handle: Clone + Hash,
{
fn width(&self) -> Length {
self.width
fn size(&self) -> Size<Length> {
Size {
width: self.width,
height: self.height,
}
fn height(&self) -> Length {
self.height
}
fn layout(

View file

@ -97,12 +97,11 @@ where
tree::State::new(State::new())
}
fn width(&self) -> Length {
self.width
fn size(&self) -> Size<Length> {
Size {
width: self.width,
height: self.height,
}
fn height(&self) -> Length {
self.height
}
fn layout(
@ -113,10 +112,11 @@ where
) -> layout::Node {
let Size { width, height } = renderer.dimensions(&self.handle);
let mut size = limits
.width(self.width)
.height(self.height)
.resolve(Size::new(width as f32, height as f32));
let mut size = limits.resolve(
self.width,
self.height,
Size::new(width as f32, height as f32),
);
let expansion_size = if height > width {
self.width

View file

@ -8,7 +8,7 @@ use crate::core::widget::tree::{self, Tree};
use crate::core::widget::Operation;
use crate::core::{
Alignment, Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle,
Shell, Widget,
Shell, Size, Widget,
};
/// A container that distributes its contents vertically.
@ -30,26 +30,10 @@ where
impl<'a, Key, Message, Renderer> Column<'a, Key, Message, Renderer>
where
Key: Copy + PartialEq,
Renderer: crate::core::Renderer,
{
/// Creates an empty [`Column`].
pub fn new() -> Self {
Self::with_children(Vec::new())
}
/// Creates a [`Column`] with the given elements.
pub fn with_children(
children: impl IntoIterator<Item = (Key, Element<'a, Message, Renderer>)>,
) -> Self {
let (keys, children) = children.into_iter().fold(
(Vec::new(), Vec::new()),
|(mut keys, mut children), (key, child)| {
keys.push(key);
children.push(child);
(keys, children)
},
);
Column {
spacing: 0.0,
padding: Padding::ZERO,
@ -57,11 +41,20 @@ where
height: Length::Shrink,
max_width: f32::INFINITY,
align_items: Alignment::Start,
keys,
children,
keys: Vec::new(),
children: Vec::new(),
}
}
/// Creates a [`Column`] with the given elements.
pub fn with_children(
children: impl IntoIterator<Item = (Key, Element<'a, Message, Renderer>)>,
) -> Self {
children
.into_iter()
.fold(Self::new(), |column, (key, child)| column.push(key, child))
}
/// Sets the vertical spacing _between_ elements.
///
/// Custom margins per element do not exist in iced. You should use this
@ -108,8 +101,19 @@ where
key: Key,
child: impl Into<Element<'a, Message, Renderer>>,
) -> Self {
let child = child.into();
let size = child.as_widget().size_hint();
if size.width.is_fill() {
self.width = Length::Fill;
}
if size.height.is_fill() {
self.height = Length::Fill;
}
self.keys.push(key);
self.children.push(child.into());
self.children.push(child);
self
}
}
@ -117,6 +121,7 @@ where
impl<'a, Key, Message, Renderer> Default for Column<'a, Key, Message, Renderer>
where
Key: Copy + PartialEq,
Renderer: crate::core::Renderer,
{
fn default() -> Self {
Self::new()
@ -173,12 +178,11 @@ where
}
}
fn width(&self) -> Length {
self.width
fn size(&self) -> Size<Length> {
Size {
width: self.width,
height: self.height,
}
fn height(&self) -> Length {
self.height
}
fn layout(
@ -196,6 +200,8 @@ where
layout::flex::Axis::Vertical,
renderer,
&limits,
self.width,
self.height,
self.padding,
self.spacing,
self.align_items,

View file

@ -142,12 +142,15 @@ where
}
}
fn width(&self) -> Length {
self.with_element(|element| element.as_widget().width())
fn size(&self) -> Size<Length> {
self.with_element(|element| element.as_widget().size())
}
fn height(&self) -> Length {
self.with_element(|element| element.as_widget().height())
fn size_hint(&self) -> Size<Length> {
Size {
width: Length::Shrink,
height: Length::Shrink,
}
}
fn layout(

View file

@ -244,12 +244,15 @@ where
self.rebuild_element_if_necessary();
}
fn width(&self) -> Length {
self.with_element(|element| element.as_widget().width())
fn size(&self) -> Size<Length> {
self.with_element(|element| element.as_widget().size())
}
fn height(&self) -> Length {
self.with_element(|element| element.as_widget().height())
fn size_hint(&self) -> Size<Length> {
Size {
width: Length::Shrink,
height: Length::Shrink,
}
}
fn layout(

View file

@ -6,6 +6,7 @@ use std::hash::Hash;
/// Creates a new [`Lazy`] widget with the given data `Dependency` and a
/// closure that can turn this data into a widget tree.
#[cfg(feature = "lazy")]
pub fn lazy<'a, Message, Renderer, Dependency, View>(
dependency: Dependency,
view: impl Fn(&Dependency) -> View + 'a,
@ -19,6 +20,7 @@ where
/// Turns an implementor of [`Component`] into an [`Element`] that can be
/// embedded in any application.
#[cfg(feature = "lazy")]
pub fn component<'a, C, Message, Renderer>(
component: C,
) -> Element<'a, Message, Renderer>
@ -37,6 +39,7 @@ where
/// The `view` closure will be provided with the current [`Size`] of
/// the [`Responsive`] widget and, therefore, can be used to build the
/// contents of the widget in a responsive way.
#[cfg(feature = "lazy")]
pub fn responsive<'a, Message, Renderer>(
f: impl Fn(Size) -> Element<'a, Message, Renderer> + 'a,
) -> Responsive<'a, Message, Renderer>

View file

@ -135,12 +135,11 @@ where
})
}
fn width(&self) -> Length {
Length::Fill
fn size(&self) -> Size<Length> {
Size {
width: Length::Fill,
height: Length::Fill,
}
fn height(&self) -> Length {
Length::Fill
}
fn layout(

View file

@ -8,7 +8,7 @@ use crate::core::renderer;
use crate::core::touch;
use crate::core::widget::{tree, Operation, Tree};
use crate::core::{
Clipboard, Element, Layout, Length, Rectangle, Shell, Widget,
Clipboard, Element, Layout, Length, Rectangle, Shell, Size, Widget,
};
/// Emit messages on mouse events.
@ -110,12 +110,8 @@ where
tree.diff_children(std::slice::from_ref(&self.content));
}
fn width(&self) -> Length {
self.content.as_widget().width()
}
fn height(&self) -> Length {
self.content.as_widget().height()
fn size(&self) -> Size<Length> {
self.content.as_widget().size()
}
fn layout(

View file

@ -254,15 +254,14 @@ where
)
.width(self.width);
let mut node = self.container.layout(self.state, renderer, &limits);
let node = self.container.layout(self.state, renderer, &limits);
let size = node.size();
node.move_to(if space_below > space_above {
position + Vector::new(0.0, self.target_height)
} else {
position - Vector::new(0.0, node.size().height)
});
node
position - Vector::new(0.0, size.height)
})
}
fn on_event(
@ -343,12 +342,11 @@ where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
{
fn width(&self) -> Length {
Length::Fill
fn size(&self) -> Size<Length> {
Size {
width: Length::Fill,
height: Length::Shrink,
}
fn height(&self) -> Length {
Length::Shrink
}
fn layout(
@ -359,7 +357,6 @@ where
) -> layout::Node {
use std::f32;
let limits = limits.width(Length::Fill).height(Length::Shrink);
let text_size =
self.text_size.unwrap_or_else(|| renderer.default_size());
@ -372,7 +369,7 @@ where
* self.options.len() as f32,
);
limits.resolve(intrinsic)
limits.resolve(Length::Fill, Length::Shrink, intrinsic)
};
layout::Node::new(size)

View file

@ -265,12 +265,11 @@ where
}
}
fn width(&self) -> Length {
self.width
fn size(&self) -> Size<Length> {
Size {
width: self.width,
height: self.height,
}
fn height(&self) -> Length {
self.height
}
fn layout(
@ -490,8 +489,7 @@ pub fn layout<Renderer, T>(
&layout::Limits,
) -> layout::Node,
) -> layout::Node {
let limits = limits.width(width).height(height);
let size = limits.resolve(Size::ZERO);
let size = limits.resolve(width, height, Size::ZERO);
let regions = node.pane_regions(spacing, size);
let children = contents
@ -500,16 +498,14 @@ pub fn layout<Renderer, T>(
let region = regions.get(&pane)?;
let size = Size::new(region.width, region.height);
let mut node = layout_content(
let node = layout_content(
content,
tree,
renderer,
&layout::Limits::new(size, size),
);
node.move_to(Point::new(region.x, region.y));
Some(node)
Some(node.move_to(Point::new(region.x, region.y)))
})
.collect();
@ -531,6 +527,8 @@ pub fn update<'a, Message, T: Draggable>(
on_drag: &Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
on_resize: &Option<(f32, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>,
) -> event::Status {
const DRAG_DEADBAND_DISTANCE: f32 = 10.0;
let mut event_status = event::Status::Ignored;
match event {
@ -572,7 +570,6 @@ pub fn update<'a, Message, T: Draggable>(
shell,
contents,
on_click,
on_drag,
);
}
}
@ -584,7 +581,6 @@ pub fn update<'a, Message, T: Draggable>(
shell,
contents,
on_click,
on_drag,
);
}
}
@ -637,7 +633,49 @@ pub fn update<'a, Message, T: Draggable>(
}
Event::Mouse(mouse::Event::CursorMoved { .. })
| Event::Touch(touch::Event::FingerMoved { .. }) => {
if let Some((_, on_resize)) = on_resize {
if let Some((_, origin)) = action.clicked_pane() {
if let Some(on_drag) = &on_drag {
let bounds = layout.bounds();
if let Some(cursor_position) = cursor.position_over(bounds)
{
let mut clicked_region = contents
.zip(layout.children())
.filter(|(_, layout)| {
layout.bounds().contains(cursor_position)
});
if let Some(((pane, content), layout)) =
clicked_region.next()
{
if content
.can_be_dragged_at(layout, cursor_position)
{
let pane_position = layout.position();
let new_origin = cursor_position
- Vector::new(
pane_position.x,
pane_position.y,
);
if new_origin.distance(origin)
> DRAG_DEADBAND_DISTANCE
{
*action = state::Action::Dragging {
pane,
origin,
};
shell.publish(on_drag(DragEvent::Picked {
pane,
}));
}
}
}
}
}
} else if let Some((_, on_resize)) = on_resize {
if let Some((split, _)) = action.picked_split() {
let bounds = layout.bounds();
@ -712,7 +750,6 @@ fn click_pane<'a, Message, T>(
shell: &mut Shell<'_, Message>,
contents: impl Iterator<Item = (Pane, T)>,
on_click: &Option<Box<dyn Fn(Pane) -> Message + 'a>>,
on_drag: &Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
) where
T: Draggable,
{
@ -720,23 +757,15 @@ fn click_pane<'a, Message, T>(
.zip(layout.children())
.filter(|(_, layout)| layout.bounds().contains(cursor_position));
if let Some(((pane, content), layout)) = clicked_region.next() {
if let Some(((pane, _), layout)) = clicked_region.next() {
if let Some(on_click) = &on_click {
shell.publish(on_click(pane));
}
if let Some(on_drag) = &on_drag {
if content.can_be_dragged_at(layout, cursor_position) {
let pane_position = layout.position();
let origin = cursor_position
- Vector::new(pane_position.x, pane_position.y);
*action = state::Action::Dragging { pane, origin };
shell.publish(on_drag(DragEvent::Picked { pane }));
}
}
let origin =
cursor_position - Vector::new(pane_position.x, pane_position.y);
*action = state::Action::Clicking { pane, origin };
}
}
@ -749,7 +778,7 @@ pub fn mouse_interaction(
spacing: f32,
resize_leeway: Option<f32>,
) -> Option<mouse::Interaction> {
if action.picked_pane().is_some() {
if action.clicked_pane().is_some() || action.picked_pane().is_some() {
return Some(mouse::Interaction::Grabbing);
}

View file

@ -165,7 +165,7 @@ where
let title_bar_size = title_bar_layout.size();
let mut body_layout = self.body.as_widget().layout(
let body_layout = self.body.as_widget().layout(
&mut tree.children[0],
renderer,
&layout::Limits::new(
@ -177,11 +177,12 @@ where
),
);
body_layout.move_to(Point::new(0.0, title_bar_size.height));
layout::Node::with_children(
max_size,
vec![title_bar_layout, body_layout],
vec![
title_bar_layout,
body_layout.move_to(Point::new(0.0, title_bar_size.height)),
],
)
} else {
self.body.as_widget().layout(

View file

@ -403,6 +403,15 @@ pub enum Action {
///
/// [`PaneGrid`]: super::PaneGrid
Idle,
/// A [`Pane`] in the [`PaneGrid`] is being clicked.
///
/// [`PaneGrid`]: super::PaneGrid
Clicking {
/// The [`Pane`] being clicked.
pane: Pane,
/// The starting [`Point`] of the click interaction.
origin: Point,
},
/// A [`Pane`] in the [`PaneGrid`] is being dragged.
///
/// [`PaneGrid`]: super::PaneGrid
@ -432,6 +441,14 @@ impl Action {
}
}
/// Returns the current [`Pane`] that is being clicked, if any.
pub fn clicked_pane(&self) -> Option<(Pane, Point)> {
match *self {
Action::Clicking { pane, origin, .. } => Some((pane, origin)),
_ => None,
}
}
/// Returns the current [`Split`] that is being dragged, if any.
pub fn picked_split(&self) -> Option<(Split, Axis)> {
match *self {

View file

@ -217,7 +217,7 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let limits = limits.pad(self.padding);
let limits = limits.shrink(self.padding);
let max_size = limits.max();
let title_layout = self.content.as_widget().layout(
@ -228,8 +228,8 @@ where
let title_size = title_layout.size();
let mut node = if let Some(controls) = &self.controls {
let mut controls_layout = controls.as_widget().layout(
let node = if let Some(controls) = &self.controls {
let controls_layout = controls.as_widget().layout(
&mut tree.children[1],
renderer,
&layout::Limits::new(Size::ZERO, max_size),
@ -240,11 +240,13 @@ where
let height = title_size.height.max(controls_size.height);
controls_layout.move_to(Point::new(space_before_controls, 0.0));
layout::Node::with_children(
Size::new(max_size.width, height),
vec![title_layout, controls_layout],
vec![
title_layout,
controls_layout
.move_to(Point::new(space_before_controls, 0.0)),
],
)
} else {
layout::Node::with_children(
@ -253,9 +255,7 @@ where
)
};
node.move_to(Point::new(self.padding.left, self.padding.top));
layout::Node::with_children(node.size().pad(self.padding), vec![node])
layout::Node::container(node, self.padding)
}
pub(crate) fn operate(

View file

@ -45,7 +45,7 @@ where
impl<'a, T: 'a, Message, Renderer> PickList<'a, T, Message, Renderer>
where
T: ToString + Eq,
T: ToString + PartialEq,
[T]: ToOwned<Owned = Vec<T>>,
Renderer: text::Renderer,
Renderer::Theme: StyleSheet
@ -145,7 +145,7 @@ where
impl<'a, T: 'a, Message, Renderer> Widget<Message, Renderer>
for PickList<'a, T, Message, Renderer>
where
T: Clone + ToString + Eq + 'static,
T: Clone + ToString + PartialEq + 'static,
[T]: ToOwned<Owned = Vec<T>>,
Message: 'a,
Renderer: text::Renderer + 'a,
@ -164,12 +164,11 @@ where
tree::State::new(State::<Renderer::Paragraph>::new())
}
fn width(&self) -> Length {
self.width
fn size(&self) -> Size<Length> {
Size {
width: self.width,
height: Length::Shrink,
}
fn height(&self) -> Length {
Length::Shrink
}
fn layout(
@ -282,7 +281,7 @@ where
impl<'a, T: 'a, Message, Renderer> From<PickList<'a, T, Message, Renderer>>
for Element<'a, Message, Renderer>
where
T: Clone + ToString + Eq + 'static,
T: Clone + ToString + PartialEq + 'static,
[T]: ToOwned<Owned = Vec<T>>,
Message: 'a,
Renderer: text::Renderer + 'a,
@ -393,7 +392,6 @@ where
{
use std::f32;
let limits = limits.width(width).height(Length::Shrink).pad(padding);
let font = font.unwrap_or_else(|| renderer.default_font());
let text_size = text_size.unwrap_or_else(|| renderer.default_size());
@ -451,7 +449,11 @@ where
f32::from(text_line_height.to_absolute(text_size)),
);
limits.resolve(intrinsic).pad(padding)
limits
.width(width)
.shrink(padding)
.resolve(width, Length::Shrink, intrinsic)
.expand(padding)
};
layout::Node::new(size)

View file

@ -85,12 +85,11 @@ where
Renderer: crate::core::Renderer,
Renderer::Theme: StyleSheet,
{
fn width(&self) -> Length {
self.width
fn size(&self) -> Size<Length> {
Size {
width: self.width,
height: self.height.unwrap_or(Length::Fixed(Self::DEFAULT_HEIGHT)),
}
fn height(&self) -> Length {
self.height.unwrap_or(Length::Fixed(Self::DEFAULT_HEIGHT))
}
fn layout(
@ -99,13 +98,11 @@ where
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let limits = limits
.width(self.width)
.height(self.height.unwrap_or(Length::Fixed(Self::DEFAULT_HEIGHT)));
let size = limits.resolve(Size::ZERO);
layout::Node::new(size)
layout::atomic(
limits,
self.width,
self.height.unwrap_or(Length::Fixed(Self::DEFAULT_HEIGHT)),
)
}
fn draw(

View file

@ -50,12 +50,11 @@ impl<'a> QRCode<'a> {
}
impl<'a, Message, Theme> Widget<Message, Renderer<Theme>> for QRCode<'a> {
fn width(&self) -> Length {
Length::Shrink
fn size(&self) -> Size<Length> {
Size {
width: Length::Shrink,
height: Length::Shrink,
}
fn height(&self) -> Length {
Length::Shrink
}
fn layout(

View file

@ -201,12 +201,11 @@ where
tree::State::new(widget::text::State::<Renderer::Paragraph>::default())
}
fn width(&self) -> Length {
self.width
fn size(&self) -> Size<Length> {
Size {
width: self.width,
height: Length::Shrink,
}
fn height(&self) -> Length {
Length::Shrink
}
fn layout(

View file

@ -7,7 +7,7 @@ use crate::core::renderer;
use crate::core::widget::{Operation, Tree};
use crate::core::{
Alignment, Clipboard, Element, Length, Padding, Pixels, Rectangle, Shell,
Widget,
Size, Widget,
};
/// A container that distributes its contents horizontally.
@ -21,26 +21,29 @@ pub struct Row<'a, Message, Renderer = crate::Renderer> {
children: Vec<Element<'a, Message, Renderer>>,
}
impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
impl<'a, Message, Renderer> Row<'a, Message, Renderer>
where
Renderer: crate::core::Renderer,
{
/// Creates an empty [`Row`].
pub fn new() -> Self {
Self::with_children(Vec::new())
}
/// Creates a [`Row`] with the given elements.
pub fn with_children(
children: Vec<Element<'a, Message, Renderer>>,
) -> Self {
Row {
spacing: 0.0,
padding: Padding::ZERO,
width: Length::Shrink,
height: Length::Shrink,
align_items: Alignment::Start,
children,
children: Vec::new(),
}
}
/// Creates a [`Row`] with the given elements.
pub fn with_children(
children: impl IntoIterator<Item = Element<'a, Message, Renderer>>,
) -> Self {
children.into_iter().fold(Self::new(), Self::push)
}
/// Sets the horizontal spacing _between_ elements.
///
/// Custom margins per element do not exist in iced. You should use this
@ -80,12 +83,26 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
mut self,
child: impl Into<Element<'a, Message, Renderer>>,
) -> Self {
self.children.push(child.into());
let child = child.into();
let size = child.as_widget().size_hint();
if size.width.is_fill() {
self.width = Length::Fill;
}
if size.height.is_fill() {
self.height = Length::Fill;
}
self.children.push(child);
self
}
}
impl<'a, Message, Renderer> Default for Row<'a, Message, Renderer> {
impl<'a, Message, Renderer> Default for Row<'a, Message, Renderer>
where
Renderer: crate::core::Renderer,
{
fn default() -> Self {
Self::new()
}
@ -104,12 +121,11 @@ where
tree.diff_children(&self.children);
}
fn width(&self) -> Length {
self.width
fn size(&self) -> Size<Length> {
Size {
width: self.width,
height: self.height,
}
fn height(&self) -> Length {
self.height
}
fn layout(
@ -118,12 +134,12 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let limits = limits.width(self.width).height(self.height);
layout::flex::resolve(
layout::flex::Axis::Horizontal,
renderer,
&limits,
limits,
self.width,
self.height,
self.padding,
self.spacing,
self.align_items,

View file

@ -62,12 +62,11 @@ where
Renderer: crate::core::Renderer,
Renderer::Theme: StyleSheet,
{
fn width(&self) -> Length {
self.width
fn size(&self) -> Size<Length> {
Size {
width: self.width,
height: self.height,
}
fn height(&self) -> Length {
self.height
}
fn layout(
@ -76,9 +75,7 @@ where
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let limits = limits.width(self.width).height(self.height);
layout::Node::new(limits.resolve(Size::ZERO))
layout::atomic(limits, self.width, self.height)
}
fn draw(

View file

@ -220,12 +220,11 @@ where
tree.diff_children(std::slice::from_ref(&self.content));
}
fn width(&self) -> Length {
self.width
fn size(&self) -> Size<Length> {
Size {
width: self.width,
height: self.height,
}
fn height(&self) -> Length {
self.height
}
fn layout(
@ -470,8 +469,7 @@ pub fn layout<Renderer>(
direction: &Direction,
layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
) -> layout::Node {
let limits = limits.width(width).height(height);
layout::contained(limits, width, height, |limits| {
let child_limits = layout::Limits::new(
Size::new(limits.min().width, limits.min().height),
Size::new(
@ -488,10 +486,8 @@ pub fn layout<Renderer>(
),
);
let content = layout_content(renderer, &child_limits);
let size = limits.resolve(content.size());
layout::Node::with_children(size, vec![content])
layout_content(renderer, &child_limits)
})
}
/// Processes an [`Event`] and updates the [`State`] of a [`Scrollable`]

View file

@ -70,12 +70,11 @@ where
tree::State::new(P::State::default())
}
fn width(&self) -> Length {
self.width
fn size(&self) -> Size<Length> {
Size {
width: self.width,
height: self.height,
}
fn height(&self) -> Length {
self.height
}
fn layout(
@ -84,10 +83,7 @@ where
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let limits = limits.width(self.width).height(self.height);
let size = limits.resolve(Size::ZERO);
layout::Node::new(size)
layout::atomic(limits, self.width, self.height)
}
fn on_event(

View file

@ -159,12 +159,11 @@ where
tree::State::new(State::new())
}
fn width(&self) -> Length {
self.width
fn size(&self) -> Size<Length> {
Size {
width: self.width,
height: Length::Shrink,
}
fn height(&self) -> Length {
Length::Shrink
}
fn layout(
@ -173,10 +172,7 @@ where
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let limits = limits.width(self.width).height(self.height);
let size = limits.resolve(Size::ZERO);
layout::Node::new(size)
layout::atomic(limits, self.width, self.height)
}
fn on_event(

View file

@ -45,12 +45,11 @@ impl<Message, Renderer> Widget<Message, Renderer> for Space
where
Renderer: core::Renderer,
{
fn width(&self) -> Length {
self.width
fn size(&self) -> Size<Length> {
Size {
width: self.width,
height: self.height,
}
fn height(&self) -> Length {
self.height
}
fn layout(
@ -59,9 +58,7 @@ where
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let limits = limits.width(self.width).height(self.height);
layout::Node::new(limits.resolve(Size::ZERO))
layout::atomic(limits, self.width, self.height)
}
fn draw(

View file

@ -96,12 +96,11 @@ where
Renderer: svg::Renderer,
Renderer::Theme: iced_style::svg::StyleSheet,
{
fn width(&self) -> Length {
self.width
fn size(&self) -> Size<Length> {
Size {
width: self.width,
height: self.height,
}
fn height(&self) -> Length {
self.height
}
fn layout(
@ -115,10 +114,7 @@ where
let image_size = Size::new(width as f32, height as f32);
// The size to be available to the widget prior to `Shrink`ing
let raw_size = limits
.width(self.width)
.height(self.height)
.resolve(image_size);
let raw_size = limits.resolve(self.width, self.height, image_size);
// The uncropped size of the image when fit to the bounds above
let full_size = self.content_fit.fit(image_size, raw_size);

View file

@ -9,7 +9,7 @@ use crate::core::text::highlighter::{self, Highlighter};
use crate::core::text::{self, LineHeight};
use crate::core::widget::{self, Widget};
use crate::core::{
Clipboard, Color, Element, Length, Padding, Pixels, Rectangle, Shell,
Clipboard, Color, Element, Length, Padding, Pixels, Rectangle, Shell, Size,
Vector,
};
@ -316,12 +316,11 @@ where
})
}
fn width(&self) -> Length {
self.width
fn size(&self) -> Size<Length> {
Size {
width: self.width,
height: self.height,
}
fn height(&self) -> Length {
self.height
}
fn layout(
@ -350,7 +349,7 @@ where
}
internal.editor.update(
limits.pad(self.padding).max(),
limits.shrink(self.padding).max(),
self.font.unwrap_or_else(|| renderer.default_font()),
self.text_size.unwrap_or_else(|| renderer.default_size()),
self.line_height,

View file

@ -283,12 +283,11 @@ where
}
}
fn width(&self) -> Length {
self.width
fn size(&self) -> Size<Length> {
Size {
width: self.width,
height: Length::Shrink,
}
fn height(&self) -> Length {
Length::Shrink
}
fn layout(
@ -506,14 +505,11 @@ where
{
let font = font.unwrap_or_else(|| renderer.default_font());
let text_size = size.unwrap_or_else(|| renderer.default_size());
let padding = padding.fit(Size::ZERO, limits.max());
let limits = limits
.width(width)
.pad(padding)
.height(line_height.to_absolute(text_size));
let height = line_height.to_absolute(text_size);
let text_bounds = limits.resolve(Size::ZERO);
let limits = limits.width(width).shrink(padding);
let text_bounds = limits.resolve(width, height, Size::ZERO);
let placeholder_text = Text {
font,
@ -552,41 +548,41 @@ where
let icon_width = state.icon.min_width();
let mut text_node = layout::Node::new(
text_bounds - Size::new(icon_width + icon.spacing, 0.0),
);
let mut icon_node =
layout::Node::new(Size::new(icon_width, text_bounds.height));
match icon.side {
Side::Left => {
text_node.move_to(Point::new(
let (text_position, icon_position) = match icon.side {
Side::Left => (
Point::new(
padding.left + icon_width + icon.spacing,
padding.top,
));
icon_node.move_to(Point::new(padding.left, padding.top));
}
Side::Right => {
text_node.move_to(Point::new(padding.left, padding.top));
icon_node.move_to(Point::new(
),
Point::new(padding.left, padding.top),
),
Side::Right => (
Point::new(padding.left, padding.top),
Point::new(
padding.left + text_bounds.width - icon_width,
padding.top,
));
}
),
),
};
let text_node = layout::Node::new(
text_bounds - Size::new(icon_width + icon.spacing, 0.0),
)
.move_to(text_position);
let icon_node =
layout::Node::new(Size::new(icon_width, text_bounds.height))
.move_to(icon_position);
layout::Node::with_children(
text_bounds.pad(padding),
text_bounds.expand(padding),
vec![text_node, icon_node],
)
} else {
let mut text = layout::Node::new(text_bounds);
text.move_to(Point::new(padding.left, padding.top));
let text = layout::Node::new(text_bounds)
.move_to(Point::new(padding.left, padding.top));
layout::Node::with_children(text_bounds.pad(padding), vec![text])
layout::Node::with_children(text_bounds.expand(padding), vec![text])
}
}
@ -1192,6 +1188,7 @@ pub fn draw<Renderer>(
(None, 0.0)
};
let draw = |renderer: &mut Renderer, viewport| {
if let Some((cursor, color)) = cursor {
renderer.with_translation(Vector::new(-offset, 0.0), |renderer| {
renderer.fill_quad(cursor, color);
@ -1215,8 +1212,15 @@ pub fn draw<Renderer>(
} else {
theme.value_color(style)
},
text_bounds,
viewport,
);
};
if cursor.is_some() {
renderer.with_layer(text_bounds, |renderer| draw(renderer, *viewport));
} else {
draw(renderer, text_bounds);
}
}
/// Computes the current [`mouse::Interaction`] of the [`TextInput`].

View file

@ -168,12 +168,11 @@ where
tree::State::new(widget::text::State::<Renderer::Paragraph>::default())
}
fn width(&self) -> Length {
self.width
fn size(&self) -> Size<Length> {
Size {
width: self.width,
height: Length::Shrink,
}
fn height(&self) -> Length {
Length::Shrink
}
fn layout(

View file

@ -64,6 +64,12 @@ where
self
}
/// Sets the [`text::Shaping`] strategy of the [`Tooltip`].
pub fn text_shaping(mut self, shaping: text::Shaping) -> Self {
self.tooltip = self.tooltip.shaping(shaping);
self
}
/// Sets the font of the [`Tooltip`].
///
/// [`Font`]: Renderer::Font
@ -125,12 +131,8 @@ where
widget::tree::Tag::of::<State>()
}
fn width(&self) -> Length {
self.content.as_widget().width()
}
fn height(&self) -> Length {
self.content.as_widget().height()
fn size(&self) -> Size<Length> {
self.content.as_widget().size()
}
fn layout(
@ -347,7 +349,7 @@ where
.then(|| viewport.size())
.unwrap_or(Size::INFINITY),
)
.pad(Padding::new(self.padding)),
.shrink(Padding::new(self.padding)),
);
let text_bounds = text_layout.bounds();

View file

@ -156,12 +156,11 @@ where
tree::State::new(State::new())
}
fn width(&self) -> Length {
Length::Shrink
fn size(&self) -> Size<Length> {
Size {
width: Length::Shrink,
height: self.height,
}
fn height(&self) -> Length {
self.height
}
fn layout(
@ -170,10 +169,7 @@ where
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let limits = limits.width(self.width).height(self.height);
let size = limits.resolve(Size::ZERO);
layout::Node::new(size)
layout::atomic(limits, self.width, self.height)
}
fn on_event(

View file

@ -717,9 +717,19 @@ pub fn run_command<A, C, E>(
)))
.expect("Send message to event loop");
}
window::Action::FetchMaximized(_id, callback) => {
proxy
.send_event(callback(window.is_maximized()))
.expect("Send message to event loop");
}
window::Action::Maximize(_id, maximized) => {
window.set_maximized(maximized);
}
window::Action::FetchMinimized(_id, callback) => {
proxy
.send_event(callback(window.is_minimized()))
.expect("Send message to event loop");
}
window::Action::Minimize(_id, minimized) => {
window.set_minimized(minimized);
}

View file

@ -915,11 +915,25 @@ fn run_command<A, C, E>(
.expect("Send message to event loop");
}
}
window::Action::FetchMaximized(id, callback) => {
if let Some(window) = window_manager.get_mut(id) {
proxy
.send_event(callback(window.raw.is_maximized()))
.expect("Send message to event loop");
}
}
window::Action::Maximize(id, maximized) => {
if let Some(window) = window_manager.get_mut(id) {
window.raw.set_maximized(maximized);
}
}
window::Action::FetchMinimized(id, callback) => {
if let Some(window) = window_manager.get_mut(id) {
proxy
.send_event(callback(window.raw.is_minimized()))
.expect("Send message to event loop");
}
}
window::Action::Minimize(id, minimized) => {
if let Some(window) = window_manager.get_mut(id) {
window.raw.set_minimized(minimized);