diff --git a/core/src/length.rs b/core/src/length.rs index 10873e89..06d8cf0a 100644 --- a/core/src/length.rs +++ b/core/src/length.rs @@ -4,6 +4,15 @@ pub enum Length { /// Fill all the remaining space Fill, + /// Fill a portion of the remaining space relative to other elements. + /// + /// Let's say we have two elements: one with `FillPortion(2)` and one with + /// `FillPortion(3)`. The first will get 2 portions of the available space, + /// while the second one would get 3. + /// + /// `Length::Fill` is equivalent to `Length::FillPortion(1)`. + FillPortion(u16), + /// Fill the least amount of space Shrink, @@ -22,6 +31,7 @@ impl Length { pub fn fill_factor(&self) -> u16 { match self { Length::Fill => 1, + Length::FillPortion(factor) => *factor, Length::Shrink => 0, Length::Units(_) => 0, } diff --git a/examples/tour.rs b/examples/tour.rs index 2429ca4f..c7f866e8 100644 --- a/examples/tour.rs +++ b/examples/tour.rs @@ -1,7 +1,7 @@ use iced::{ button, scrollable, slider, text_input, Button, Checkbox, Color, Column, Container, Element, HorizontalAlignment, Image, Length, Radio, Row, - Sandbox, Scrollable, Settings, Slider, Text, TextInput, + Sandbox, Scrollable, Settings, Slider, Space, Text, TextInput, }; pub fn main() { @@ -68,7 +68,7 @@ impl Sandbox for Tour { ); } - controls = controls.push(Column::new()); + controls = controls.push(Space::with_width(Length::Fill)); if steps.can_continue() { controls = controls.push( diff --git a/native/src/layout/flex.rs b/native/src/layout/flex.rs index 0ed17d41..03b13e38 100644 --- a/native/src/layout/flex.rs +++ b/native/src/layout/flex.rs @@ -73,10 +73,12 @@ where Renderer: crate::Renderer, { let limits = limits.pad(padding); + let total_spacing = spacing * items.len().saturating_sub(1) as f32; + let max_cross = axis.cross(limits.max()); - let mut total_non_fill = spacing * items.len().saturating_sub(1) as f32; let mut fill_sum = 0; let mut cross = axis.cross(limits.min()); + let mut available = axis.main(limits.max()) - total_spacing; let mut nodes: Vec = Vec::with_capacity(items.len()); nodes.resize(items.len(), Node::default()); @@ -89,12 +91,15 @@ where .fill_factor(); if fill_factor == 0 { - let child_limits = Limits::new(Size::ZERO, limits.max()); + let (max_width, max_height) = axis.pack(available, max_cross); + + let child_limits = + Limits::new(Size::ZERO, Size::new(max_width, max_height)); let layout = child.layout(renderer, &child_limits); let size = layout.size(); - total_non_fill += axis.main(size); + available -= axis.main(size); cross = cross.max(axis.cross(size)); nodes[i] = layout; @@ -103,8 +108,7 @@ where } } - let available = axis.main(limits.max()); - let remaining = (available - total_non_fill).max(0.0); + let remaining = available.max(0.0); for (i, child) in items.iter().enumerate() { let fill_factor = match axis { diff --git a/native/src/layout/limits.rs b/native/src/layout/limits.rs index 2705a47d..a35f7ff7 100644 --- a/native/src/layout/limits.rs +++ b/native/src/layout/limits.rs @@ -52,7 +52,7 @@ impl Limits { Length::Shrink => { self.fill.width = self.min.width; } - Length::Fill => { + Length::Fill | Length::FillPortion(_) => { self.fill.width = self.fill.width.min(self.max.width); } Length::Units(units) => { @@ -76,7 +76,7 @@ impl Limits { Length::Shrink => { self.fill.height = self.min.height; } - Length::Fill => { + Length::Fill | Length::FillPortion(_) => { self.fill.height = self.fill.height.min(self.max.height); } Length::Units(units) => { diff --git a/native/src/mouse_cursor.rs b/native/src/mouse_cursor.rs index a4740a27..c7297e0e 100644 --- a/native/src/mouse_cursor.rs +++ b/native/src/mouse_cursor.rs @@ -22,3 +22,9 @@ pub enum MouseCursor { /// The cursor is over a text widget. Text, } + +impl Default for MouseCursor { + fn default() -> MouseCursor { + MouseCursor::OutOfBounds + } +} diff --git a/native/src/widget.rs b/native/src/widget.rs index 4aa7e7f0..536d9e94 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -29,6 +29,7 @@ pub mod radio; pub mod row; pub mod scrollable; pub mod slider; +pub mod space; pub mod svg; pub mod text; pub mod text_input; @@ -52,6 +53,8 @@ pub use scrollable::Scrollable; #[doc(no_inline)] pub use slider::Slider; #[doc(no_inline)] +pub use space::Space; +#[doc(no_inline)] pub use svg::Svg; #[doc(no_inline)] pub use text::Text; diff --git a/native/src/widget/space.rs b/native/src/widget/space.rs new file mode 100644 index 00000000..2029c52f --- /dev/null +++ b/native/src/widget/space.rs @@ -0,0 +1,104 @@ +//! Distribute content vertically. +use std::hash::Hash; + +use crate::{ + layout, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget, +}; + +/// An amount of empty space. +/// +/// It can be useful if you want to fill some space with nothing. +#[derive(Debug)] +pub struct Space { + width: Length, + height: Length, +} + +impl Space { + /// Creates an amount of empty [`Space`] with the given width and height. + /// + /// [`Space`]: struct.Space.html + pub fn new(width: Length, height: Length) -> Self { + Space { width, height } + } + + /// Creates an amount of horizontal [`Space`]. + /// + /// [`Space`]: struct.Space.html + pub fn with_width(width: Length) -> Self { + Space { + width, + height: Length::Shrink, + } + } + + /// Creates an amount of vertical [`Space`]. + /// + /// [`Space`]: struct.Space.html + pub fn with_height(height: Length) -> Self { + Space { + width: Length::Shrink, + height, + } + } +} + +impl<'a, Message, Renderer> Widget for Space +where + Renderer: self::Renderer, +{ + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + self.height + } + + fn layout( + &self, + _renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let limits = limits.width(self.width).height(self.height); + + layout::Node::new(limits.resolve(Size::ZERO)) + } + + fn draw( + &self, + renderer: &mut Renderer, + layout: Layout<'_>, + _cursor_position: Point, + ) -> Renderer::Output { + renderer.draw(layout.bounds()) + } + + fn hash_layout(&self, state: &mut Hasher) { + std::any::TypeId::of::().hash(state); + self.width.hash(state); + self.height.hash(state); + } +} + +/// The renderer of an amount of [`Space`]. +/// +/// [`Space`]: struct.Space.html +pub trait Renderer: crate::Renderer { + /// Draws an amount of empty [`Space`]. + /// + /// You should most likely return an empty primitive here. + /// + /// [`Space`]: struct.Space.html + fn draw(&mut self, bounds: Rectangle) -> Self::Output; +} + +impl<'a, Message, Renderer> From for Element<'a, Message, Renderer> +where + Renderer: self::Renderer, + Message: 'static, +{ + fn from(space: Space) -> Element<'a, Message, Renderer> { + Element::new(space) + } +} diff --git a/src/native.rs b/src/native.rs index cc2068ae..d5c9349a 100644 --- a/src/native.rs +++ b/src/native.rs @@ -1,6 +1,6 @@ pub use iced_winit::{ Align, Background, Color, Command, Font, HorizontalAlignment, Length, - Subscription, VerticalAlignment, + Space, Subscription, VerticalAlignment, }; pub mod widget { diff --git a/web/src/style.rs b/web/src/style.rs index 2fb8602a..4f72b22c 100644 --- a/web/src/style.rs +++ b/web/src/style.rs @@ -139,7 +139,7 @@ pub fn length(length: Length) -> String { match length { Length::Shrink => String::from("auto"), Length::Units(px) => format!("{}px", px), - Length::Fill => String::from("100%"), + Length::Fill | Length::FillPortion(_) => String::from("100%"), } } diff --git a/web/src/widget.rs b/web/src/widget.rs index b0e16692..0ac536bd 100644 --- a/web/src/widget.rs +++ b/web/src/widget.rs @@ -28,6 +28,7 @@ mod container; mod image; mod radio; mod row; +mod space; mod text; #[doc(no_inline)] @@ -47,6 +48,7 @@ pub use container::Container; pub use image::Image; pub use radio::Radio; pub use row::Row; +pub use space::Space; /// A component that displays information and allows interaction. /// diff --git a/web/src/widget/image.rs b/web/src/widget/image.rs index ed8b7ecf..413b663e 100644 --- a/web/src/widget/image.rs +++ b/web/src/widget/image.rs @@ -67,7 +67,7 @@ impl Widget for Image { match self.width { Length::Shrink => {} - Length::Fill => { + Length::Fill | Length::FillPortion(_) => { image = image.attr("width", "100%"); } Length::Units(px) => { diff --git a/web/src/widget/space.rs b/web/src/widget/space.rs new file mode 100644 index 00000000..baf4c80b --- /dev/null +++ b/web/src/widget/space.rs @@ -0,0 +1,69 @@ +use crate::{style, Bus, Element, Length, Widget}; +use dodrio::bumpalo; + +/// An amount of empty space. +/// +/// It can be useful if you want to fill some space with nothing. +#[derive(Debug)] +pub struct Space { + width: Length, + height: Length, +} + +impl Space { + /// Creates an amount of empty [`Space`] with the given width and height. + /// + /// [`Space`]: struct.Space.html + pub fn new(width: Length, height: Length) -> Self { + Space { width, height } + } + + /// Creates an amount of horizontal [`Space`]. + /// + /// [`Space`]: struct.Space.html + pub fn with_width(width: Length) -> Self { + Space { + width, + height: Length::Shrink, + } + } + + /// Creates an amount of vertical [`Space`]. + /// + /// [`Space`]: struct.Space.html + pub fn with_height(height: Length) -> Self { + Space { + width: Length::Shrink, + height, + } + } +} + +impl<'a, Message> Widget for Space { + fn node<'b>( + &self, + bump: &'b bumpalo::Bump, + _publish: &Bus, + _style_sheet: &mut style::Sheet<'b>, + ) -> dodrio::Node<'b> { + use dodrio::builder::*; + + let width = style::length(self.width); + let height = style::length(self.height); + + let style = bumpalo::format!( + in bump, + "width: {}; height: {};", + width, + height + ); + + div(bump).attr("style", style.into_bump_str()).finish() + } +} + +impl<'a, Message> From for Element<'a, Message> { + fn from(space: Space) -> Element<'a, Message> { + Element::new(space) + } +} diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index 958cc17f..6c61f800 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -64,3 +64,9 @@ pub enum Primitive { content: Box, }, } + +impl Default for Primitive { + fn default() -> Primitive { + Primitive::None + } +} diff --git a/wgpu/src/renderer/widget.rs b/wgpu/src/renderer/widget.rs index 8cf79cb0..daf35cbe 100644 --- a/wgpu/src/renderer/widget.rs +++ b/wgpu/src/renderer/widget.rs @@ -7,6 +7,7 @@ mod radio; mod row; mod scrollable; mod slider; +mod space; mod text; mod text_input; diff --git a/wgpu/src/renderer/widget/space.rs b/wgpu/src/renderer/widget/space.rs new file mode 100644 index 00000000..28e05437 --- /dev/null +++ b/wgpu/src/renderer/widget/space.rs @@ -0,0 +1,8 @@ +use crate::{Primitive, Renderer}; +use iced_native::{space, MouseCursor, Rectangle}; + +impl space::Renderer for Renderer { + fn draw(&mut self, _bounds: Rectangle) -> Self::Output { + (Primitive::None, MouseCursor::OutOfBounds) + } +}