diff options
Diffstat (limited to 'widget/src')
-rw-r--r-- | widget/src/container.rs | 67 | ||||
-rw-r--r-- | widget/src/helpers.rs | 22 | ||||
-rw-r--r-- | widget/src/image.rs | 79 | ||||
-rw-r--r-- | widget/src/image/viewer.rs | 6 | ||||
-rw-r--r-- | widget/src/svg.rs | 80 |
5 files changed, 201 insertions, 53 deletions
diff --git a/widget/src/container.rs b/widget/src/container.rs index 21405722..51967707 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -92,6 +92,46 @@ where self } + /// Sets the [`Container`] to fill the available space in the horizontal axis. + /// + /// This can be useful to quickly position content when chained with + /// alignment functions—like [`center_x`]. + /// + /// Calling this method is equivalent to calling [`width`] with a + /// [`Length::Fill`]. + /// + /// [`center_x`]: Self::center_x + /// [`width`]: Self::width + pub fn fill_x(self) -> Self { + self.width(Length::Fill) + } + + /// Sets the [`Container`] to fill the available space in the vetical axis. + /// + /// This can be useful to quickly position content when chained with + /// alignment functions—like [`center_y`]. + /// + /// Calling this method is equivalent to calling [`height`] with a + /// [`Length::Fill`]. + /// + /// [`center_y`]: Self::center_x + /// [`height`]: Self::height + pub fn fill_y(self) -> Self { + self.height(Length::Fill) + } + + /// Sets the [`Container`] to fill all the available space. + /// + /// Calling this method is equivalent to chaining [`fill_x`] and + /// [`fill_y`]. + /// + /// [`center`]: Self::center + /// [`fill_x`]: Self::fill_x + /// [`fill_y`]: Self::fill_y + pub fn fill(self) -> Self { + self.width(Length::Fill).height(Length::Fill) + } + /// Sets the maximum width of the [`Container`]. pub fn max_width(mut self, max_width: impl Into<Pixels>) -> Self { self.max_width = max_width.into().0; @@ -116,16 +156,27 @@ where self } - /// Centers the contents in the horizontal axis of the [`Container`]. - pub fn center_x(mut self) -> Self { - self.horizontal_alignment = alignment::Horizontal::Center; - self + /// Sets the width of the [`Container`] and centers its contents horizontally. + pub fn center_x(self, width: impl Into<Length>) -> Self { + self.width(width).align_x(alignment::Horizontal::Center) } - /// Centers the contents in the vertical axis of the [`Container`]. - pub fn center_y(mut self) -> Self { - self.vertical_alignment = alignment::Vertical::Center; - self + /// Sets the height of the [`Container`] and centers its contents vertically. + pub fn center_y(self, height: impl Into<Length>) -> Self { + self.height(height).align_y(alignment::Vertical::Center) + } + + /// Centers the contents in both the horizontal and vertical axes of the + /// [`Container`]. + /// + /// This is equivalent to chaining [`center_x`] and [`center_y`]. + /// + /// [`center_x`]: Self::center_x + /// [`center_y`]: Self::center_y + pub fn center(self, length: impl Into<Length>) -> Self { + let length = length.into(); + + self.center_x(length).center_y(length) } /// Sets whether the contents of the [`Container`] should be clipped on diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 48c5dde4..a1ecd9d1 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -78,6 +78,28 @@ where Container::new(content) } +/// Creates a new [`Container`] that fills all the available space +/// and centers its contents inside. +/// +/// This is equivalent to: +/// ```rust,no_run +/// # use iced_widget::core::Length; +/// # use iced_widget::Container; +/// # fn container<A>(x: A) -> Container<'static, ()> { unreachable!() } +/// let centered = container("Centered!").center(Length::Fill); +/// ``` +/// +/// [`Container`]: crate::Container +pub fn center<'a, Message, Theme, Renderer>( + content: impl Into<Element<'a, Message, Theme, Renderer>>, +) -> Container<'a, Message, Theme, Renderer> +where + Theme: container::Catalog + 'a, + Renderer: core::Renderer, +{ + container(content).center(Length::Fill) +} + /// Creates a new [`Column`] with the given children. pub fn column<'a, Message, Theme, Renderer>( children: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>, diff --git a/widget/src/image.rs b/widget/src/image.rs index 21d371b7..80e17263 100644 --- a/widget/src/image.rs +++ b/widget/src/image.rs @@ -8,7 +8,8 @@ use crate::core::mouse; use crate::core::renderer; use crate::core::widget::Tree; use crate::core::{ - ContentFit, Element, Layout, Length, Rectangle, Size, Vector, Widget, + ContentFit, Element, Layout, Length, Point, Rectangle, Rotation, Size, + Vector, Widget, }; pub use image::{FilterMethod, Handle}; @@ -36,6 +37,8 @@ pub struct Image<Handle> { height: Length, content_fit: ContentFit, filter_method: FilterMethod, + rotation: Rotation, + opacity: f32, } impl<Handle> Image<Handle> { @@ -45,8 +48,10 @@ impl<Handle> Image<Handle> { handle: handle.into(), width: Length::Shrink, height: Length::Shrink, - content_fit: ContentFit::Contain, + content_fit: ContentFit::default(), filter_method: FilterMethod::default(), + rotation: Rotation::default(), + opacity: 1.0, } } @@ -75,6 +80,21 @@ impl<Handle> Image<Handle> { self.filter_method = filter_method; self } + + /// Applies the given [`Rotation`] to the [`Image`]. + pub fn rotation(mut self, rotation: impl Into<Rotation>) -> Self { + self.rotation = rotation.into(); + self + } + + /// Sets the opacity of the [`Image`]. + /// + /// It should be in the [0.0, 1.0] range—`0.0` meaning completely transparent, + /// and `1.0` meaning completely opaque. + pub fn opacity(mut self, opacity: impl Into<f32>) -> Self { + self.opacity = opacity.into(); + self + } } /// Computes the layout of an [`Image`]. @@ -85,22 +105,24 @@ pub fn layout<Renderer, Handle>( width: Length, height: Length, content_fit: ContentFit, + rotation: Rotation, ) -> layout::Node where Renderer: image::Renderer<Handle = Handle>, { // The raw w/h of the underlying image - let image_size = { - let Size { width, height } = renderer.measure_image(handle); + let image_size = renderer.measure_image(handle); + let image_size = + Size::new(image_size.width as f32, image_size.height as f32); - Size::new(width as f32, height as f32) - }; + // The rotated size of the image + let rotated_size = rotation.apply(image_size); // The size to be available to the widget prior to `Shrink`ing - let raw_size = limits.resolve(width, height, image_size); + let raw_size = limits.resolve(width, height, rotated_size); // The uncropped size of the image when fit to the bounds above - let full_size = content_fit.fit(image_size, raw_size); + let full_size = content_fit.fit(rotated_size, raw_size); // Shrink the widget to fit the resized image, if requested let final_size = Size { @@ -124,32 +146,46 @@ pub fn draw<Renderer, Handle>( handle: &Handle, content_fit: ContentFit, filter_method: FilterMethod, + rotation: Rotation, + opacity: f32, ) where Renderer: image::Renderer<Handle = Handle>, Handle: Clone, { let Size { width, height } = renderer.measure_image(handle); let image_size = Size::new(width as f32, height as f32); + let rotated_size = rotation.apply(image_size); let bounds = layout.bounds(); - let adjusted_fit = content_fit.fit(image_size, bounds.size()); + let adjusted_fit = content_fit.fit(rotated_size, bounds.size()); - let render = |renderer: &mut Renderer| { - let offset = Vector::new( - (bounds.width - adjusted_fit.width).max(0.0) / 2.0, - (bounds.height - adjusted_fit.height).max(0.0) / 2.0, - ); + let scale = Vector::new( + adjusted_fit.width / rotated_size.width, + adjusted_fit.height / rotated_size.height, + ); - let drawing_bounds = Rectangle { - width: adjusted_fit.width, - height: adjusted_fit.height, - ..bounds - }; + let final_size = image_size * scale; + let position = match content_fit { + ContentFit::None => Point::new( + bounds.x + (rotated_size.width - adjusted_fit.width) / 2.0, + bounds.y + (rotated_size.height - adjusted_fit.height) / 2.0, + ), + _ => Point::new( + bounds.center_x() - final_size.width / 2.0, + bounds.center_y() - final_size.height / 2.0, + ), + }; + + let drawing_bounds = Rectangle::new(position, final_size); + + let render = |renderer: &mut Renderer| { renderer.draw_image( handle.clone(), filter_method, - drawing_bounds + offset, + drawing_bounds, + rotation.radians(), + opacity, ); }; @@ -187,6 +223,7 @@ where self.width, self.height, self.content_fit, + self.rotation, ) } @@ -206,6 +243,8 @@ where &self.handle, self.content_fit, self.filter_method, + self.rotation, + self.opacity, ); } } diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs index 2e6a7528..8fe6f021 100644 --- a/widget/src/image/viewer.rs +++ b/widget/src/image/viewer.rs @@ -6,8 +6,8 @@ use crate::core::mouse; use crate::core::renderer; use crate::core::widget::tree::{self, Tree}; use crate::core::{ - Clipboard, Element, Layout, Length, Pixels, Point, Rectangle, Shell, Size, - Vector, Widget, + Clipboard, Element, Layout, Length, Pixels, Point, Radians, Rectangle, + Shell, Size, Vector, Widget, }; /// A frame that displays an image with the ability to zoom in/out and pan. @@ -341,6 +341,8 @@ where y: bounds.y, ..Rectangle::with_size(image_size) }, + Radians(0.0), + 1.0, ); }); }); diff --git a/widget/src/svg.rs b/widget/src/svg.rs index eb142189..4551bcad 100644 --- a/widget/src/svg.rs +++ b/widget/src/svg.rs @@ -5,8 +5,8 @@ use crate::core::renderer; use crate::core::svg; use crate::core::widget::Tree; use crate::core::{ - Color, ContentFit, Element, Layout, Length, Rectangle, Size, Theme, Vector, - Widget, + Color, ContentFit, Element, Layout, Length, Point, Rectangle, Rotation, + Size, Theme, Vector, Widget, }; use std::path::PathBuf; @@ -29,6 +29,8 @@ where height: Length, content_fit: ContentFit, class: Theme::Class<'a>, + rotation: Rotation, + opacity: f32, } impl<'a, Theme> Svg<'a, Theme> @@ -43,6 +45,8 @@ where height: Length::Shrink, content_fit: ContentFit::Contain, class: Theme::default(), + rotation: Rotation::default(), + opacity: 1.0, } } @@ -95,6 +99,21 @@ where self.class = class.into(); self } + + /// Applies the given [`Rotation`] to the [`Svg`]. + pub fn rotation(mut self, rotation: impl Into<Rotation>) -> Self { + self.rotation = rotation.into(); + self + } + + /// Sets the opacity of the [`Svg`]. + /// + /// It should be in the [0.0, 1.0] range—`0.0` meaning completely transparent, + /// and `1.0` meaning completely opaque. + pub fn opacity(mut self, opacity: impl Into<f32>) -> Self { + self.opacity = opacity.into(); + self + } } impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> @@ -120,11 +139,14 @@ where let Size { width, height } = renderer.measure_svg(&self.handle); let image_size = Size::new(width as f32, height as f32); + // The rotated size of the svg + let rotated_size = self.rotation.apply(image_size); + // The size to be available to the widget prior to `Shrink`ing - let raw_size = limits.resolve(self.width, self.height, image_size); + let raw_size = limits.resolve(self.width, self.height, rotated_size); // The uncropped size of the image when fit to the bounds above - let full_size = self.content_fit.fit(image_size, raw_size); + let full_size = self.content_fit.fit(rotated_size, raw_size); // Shrink the widget to fit the resized image, if requested let final_size = Size { @@ -153,35 +175,47 @@ where ) { let Size { width, height } = renderer.measure_svg(&self.handle); let image_size = Size::new(width as f32, height as f32); + let rotated_size = self.rotation.apply(image_size); let bounds = layout.bounds(); - let adjusted_fit = self.content_fit.fit(image_size, bounds.size()); - let is_mouse_over = cursor.is_over(bounds); + let adjusted_fit = self.content_fit.fit(rotated_size, bounds.size()); + let scale = Vector::new( + adjusted_fit.width / rotated_size.width, + adjusted_fit.height / rotated_size.height, + ); + + let final_size = image_size * scale; + + let position = match self.content_fit { + ContentFit::None => Point::new( + bounds.x + (rotated_size.width - adjusted_fit.width) / 2.0, + bounds.y + (rotated_size.height - adjusted_fit.height) / 2.0, + ), + _ => Point::new( + bounds.center_x() - final_size.width / 2.0, + bounds.center_y() - final_size.height / 2.0, + ), + }; - let render = |renderer: &mut Renderer| { - let offset = Vector::new( - (bounds.width - adjusted_fit.width).max(0.0) / 2.0, - (bounds.height - adjusted_fit.height).max(0.0) / 2.0, - ); + let drawing_bounds = Rectangle::new(position, final_size); - let drawing_bounds = Rectangle { - width: adjusted_fit.width, - height: adjusted_fit.height, - ..bounds - }; + let is_mouse_over = cursor.is_over(bounds); - let status = if is_mouse_over { - Status::Hovered - } else { - Status::Idle - }; + let status = if is_mouse_over { + Status::Hovered + } else { + Status::Idle + }; - let style = theme.style(&self.class, status); + let style = theme.style(&self.class, status); + let render = |renderer: &mut Renderer| { renderer.draw_svg( self.handle.clone(), style.color, - drawing_bounds + offset, + drawing_bounds, + self.rotation.radians(), + self.opacity, ); }; |