diff --git a/core/src/element.rs b/core/src/element.rs index b7d51aeb..6f8751ab 100644 --- a/core/src/element.rs +++ b/core/src/element.rs @@ -368,12 +368,13 @@ where tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { let mapper = &self.mapper; self.widget - .overlay(tree, layout, renderer, translation) + .overlay(tree, layout, renderer, viewport, translation) .map(move |overlay| overlay.map(mapper)) } } @@ -519,10 +520,15 @@ where state: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { - self.element - .widget - .overlay(state, layout, renderer, translation) + self.element.widget.overlay( + state, + layout, + renderer, + viewport, + translation, + ) } } diff --git a/core/src/overlay.rs b/core/src/overlay.rs index 94239152..92118567 100644 --- a/core/src/overlay.rs +++ b/core/src/overlay.rs @@ -101,6 +101,16 @@ where ) -> Option> { None } + + /// The index of the overlay. + /// + /// Overlays with a higher index will be rendered on top of overlays with + /// a lower index. + /// + /// By default, it returns `1.0`. + fn index(&self) -> f32 { + 1.0 + } } /// Returns a [`Group`] of overlay [`Element`] children. @@ -112,6 +122,7 @@ pub fn from_children<'a, Message, Theme, Renderer>( tree: &'a mut Tree, layout: Layout<'_>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> where @@ -122,9 +133,13 @@ where .zip(&mut tree.children) .zip(layout.children()) .filter_map(|((child, state), layout)| { - child - .as_widget_mut() - .overlay(state, layout, renderer, translation) + child.as_widget_mut().overlay( + state, + layout, + renderer, + viewport, + translation, + ) }) .collect::>(); diff --git a/core/src/overlay/element.rs b/core/src/overlay/element.rs index de6e73fd..f696d2d6 100644 --- a/core/src/overlay/element.rs +++ b/core/src/overlay/element.rs @@ -23,6 +23,18 @@ where Self { overlay } } + /// Returns a reference to the [`Overlay`] of the [`Element`], + pub fn as_overlay(&self) -> &dyn Overlay { + self.overlay.as_ref() + } + + /// Returns a mutable reference to the [`Overlay`] of the [`Element`], + pub fn as_overlay_mut( + &mut self, + ) -> &mut dyn Overlay { + self.overlay.as_mut() + } + /// Applies a transformation to the produced message of the [`Element`]. pub fn map( self, @@ -38,82 +50,6 @@ where overlay: Box::new(Map::new(self.overlay, f)), } } - - /// Computes the layout of the [`Element`] in the given bounds. - pub fn layout( - &mut self, - renderer: &Renderer, - bounds: Size, - ) -> layout::Node { - self.overlay.layout(renderer, bounds) - } - - /// Processes a runtime [`Event`]. - pub fn update( - &mut self, - event: &Event, - layout: Layout<'_>, - cursor: mouse::Cursor, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) { - self.overlay - .update(event, layout, cursor, renderer, clipboard, shell); - } - - /// Returns the current [`mouse::Interaction`] of the [`Element`]. - pub fn mouse_interaction( - &self, - layout: Layout<'_>, - cursor: mouse::Cursor, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - self.overlay - .mouse_interaction(layout, cursor, viewport, renderer) - } - - /// Draws the [`Element`] and its children using the given [`Layout`]. - pub fn draw( - &self, - renderer: &mut Renderer, - theme: &Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor: mouse::Cursor, - ) { - self.overlay.draw(renderer, theme, style, layout, cursor); - } - - /// Applies a [`widget::Operation`] to the [`Element`]. - pub fn operate( - &mut self, - layout: Layout<'_>, - renderer: &Renderer, - operation: &mut dyn widget::Operation, - ) { - self.overlay.operate(layout, renderer, operation); - } - - /// Returns true if the cursor is over the [`Element`]. - pub fn is_over( - &self, - layout: Layout<'_>, - renderer: &Renderer, - cursor_position: Point, - ) -> bool { - self.overlay.is_over(layout, renderer, cursor_position) - } - - /// Returns the nested overlay of the [`Element`], if there is any. - pub fn overlay<'b>( - &'b mut self, - layout: Layout<'_>, - renderer: &Renderer, - ) -> Option> { - self.overlay.overlay(layout, renderer) - } } struct Map<'a, A, B, Theme, Renderer> { diff --git a/core/src/overlay/group.rs b/core/src/overlay/group.rs index 970c1b0e..a0afe3be 100644 --- a/core/src/overlay/group.rs +++ b/core/src/overlay/group.rs @@ -25,18 +25,18 @@ where /// Creates a [`Group`] with the given elements. pub fn with_children( - children: Vec>, + mut children: Vec>, ) -> Self { - Group { children } - } + use std::cmp; - /// Adds an [`overlay::Element`] to the [`Group`]. - pub fn push( - mut self, - child: impl Into>, - ) -> Self { - self.children.push(child.into()); - self + children.sort_unstable_by(|a, b| { + a.as_overlay() + .index() + .partial_cmp(&b.as_overlay().index()) + .unwrap_or(cmp::Ordering::Equal) + }); + + Group { children } } /// Turns the [`Group`] into an overlay [`overlay::Element`]. @@ -67,7 +67,7 @@ where bounds, self.children .iter_mut() - .map(|child| child.layout(renderer, bounds)) + .map(|child| child.as_overlay_mut().layout(renderer, bounds)) .collect(), ) } @@ -82,7 +82,9 @@ where shell: &mut Shell<'_, Message>, ) { for (child, layout) in self.children.iter_mut().zip(layout.children()) { - child.update(event, layout, cursor, renderer, clipboard, shell); + child + .as_overlay_mut() + .update(event, layout, cursor, renderer, clipboard, shell); } } @@ -95,7 +97,9 @@ where cursor: mouse::Cursor, ) { for (child, layout) in self.children.iter().zip(layout.children()) { - child.draw(renderer, theme, style, layout, cursor); + child + .as_overlay() + .draw(renderer, theme, style, layout, cursor); } } @@ -110,7 +114,9 @@ where .iter() .zip(layout.children()) .map(|(child, layout)| { - child.mouse_interaction(layout, cursor, viewport, renderer) + child + .as_overlay() + .mouse_interaction(layout, cursor, viewport, renderer) }) .max() .unwrap_or_default() @@ -125,7 +131,7 @@ where operation.container(None, layout.bounds(), &mut |operation| { self.children.iter_mut().zip(layout.children()).for_each( |(child, layout)| { - child.operate(layout, renderer, operation); + child.as_overlay_mut().operate(layout, renderer, operation); }, ); }); @@ -141,7 +147,9 @@ where .iter() .zip(layout.children()) .any(|(child, layout)| { - child.is_over(layout, renderer, cursor_position) + child + .as_overlay() + .is_over(layout, renderer, cursor_position) }) } @@ -154,11 +162,20 @@ where .children .iter_mut() .zip(layout.children()) - .filter_map(|(child, layout)| child.overlay(layout, renderer)) + .filter_map(|(child, layout)| { + child.as_overlay_mut().overlay(layout, renderer) + }) .collect::>(); (!children.is_empty()).then(|| Group::with_children(children).overlay()) } + + fn index(&self) -> f32 { + self.children + .first() + .map(|child| child.as_overlay().index()) + .unwrap_or(1.0) + } } impl<'a, Message, Theme, Renderer> From> diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index 14d2a2e8..1984394c 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -157,6 +157,30 @@ impl Rectangle { distance_x.hypot(distance_y) } + /// Computes the offset that must be applied to the [`Rectangle`] to be placed + /// inside the given `container`. + pub fn offset(&self, container: &Rectangle) -> Vector { + let Some(intersection) = self.intersection(container) else { + return Vector::ZERO; + }; + + let left = intersection.x - self.x; + let top = intersection.y - self.y; + + Vector::new( + if left > 0.0 { + left + } else { + intersection.x + intersection.width - self.x - self.width + }, + if top > 0.0 { + top + } else { + intersection.y + intersection.height - self.y - self.height + }, + ) + } + /// Returns true if the current [`Rectangle`] is completely within the given /// `container`. pub fn is_within(&self, container: &Rectangle) -> bool { @@ -268,6 +292,17 @@ impl Rectangle { Self::new(position, size) } + + /// Scales the [`Rectangle`] without changing its position, effectively + /// "zooming" it. + pub fn zoom(self, zoom: f32) -> Self { + Self { + x: self.x - (self.width * (zoom - 1.0)) / 2.0, + y: self.y - (self.height * (zoom - 1.0)) / 2.0, + width: self.width * zoom, + height: self.height * zoom, + } + } } impl std::ops::Mul for Rectangle { diff --git a/core/src/widget.rs b/core/src/widget.rs index 807b14ac..72985e3e 100644 --- a/core/src/widget.rs +++ b/core/src/widget.rs @@ -146,6 +146,7 @@ where _state: &'a mut Tree, _layout: Layout<'_>, _renderer: &Renderer, + _viewport: &Rectangle, _translation: Vector, ) -> Option> { None diff --git a/examples/gallery/src/main.rs b/examples/gallery/src/main.rs index 0d52483b..709771c1 100644 --- a/examples/gallery/src/main.rs +++ b/examples/gallery/src/main.rs @@ -14,8 +14,8 @@ use iced::widget::{ }; use iced::window; use iced::{ - Animation, ContentFit, Element, Fill, Function, Subscription, Task, Theme, - color, + Animation, Color, ContentFit, Element, Fill, Function, Shadow, + Subscription, Task, Theme, color, }; use std::collections::HashMap; @@ -210,6 +210,22 @@ fn card<'a>( .content_fit(ContentFit::Cover) .opacity(thumbnail.fade_in.interpolate(0.0, 1.0, now)) .scale(thumbnail.zoom.interpolate(1.0, 1.1, now)) + .translate(move |bounds, viewport| { + bounds.zoom(1.1).offset(&viewport.shrink(10)) + * thumbnail.zoom.interpolate(0.0, 1.0, now) + }) + .style(move |_theme| image::Style { + shadow: Shadow { + color: Color::BLACK.scale_alpha( + thumbnail.zoom.interpolate(0.0, 1.0, now), + ), + blur_radius: thumbnail + .zoom + .interpolate(0.0, 20.0, now), + ..Shadow::default() + }, + ..image::Style::default() + }) .into() } else { horizontal_space().into() diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index 87d4f107..ef9418d8 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -425,6 +425,7 @@ mod toast { state: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { let instants = state.state.downcast_mut::>>(); @@ -435,6 +436,7 @@ mod toast { &mut content_state[0], layout, renderer, + viewport, translation, ); diff --git a/runtime/src/overlay/nested.rs b/runtime/src/overlay/nested.rs index 38054d7b..a577b903 100644 --- a/runtime/src/overlay/nested.rs +++ b/runtime/src/overlay/nested.rs @@ -39,10 +39,11 @@ where where Renderer: renderer::Renderer, { - let node = element.layout(renderer, bounds); + let overlay = element.as_overlay_mut(); + let node = overlay.layout(renderer, bounds); if let Some(mut nested) = - element.overlay(Layout::new(&node), renderer) + overlay.overlay(Layout::new(&node), renderer) { layout::Node::with_children( node.size(), @@ -79,13 +80,14 @@ where if let Some(layout) = layouts.next() { let nested_layout = layouts.next(); + let overlay = element.as_overlay_mut(); let is_over = cursor .position() .zip(nested_layout) .and_then(|(cursor_position, nested_layout)| { - element.overlay(layout, renderer).map(|nested| { - nested.is_over( + overlay.overlay(layout, renderer).map(|nested| { + nested.as_overlay().is_over( nested_layout.children().next().unwrap(), renderer, cursor_position, @@ -95,7 +97,7 @@ where .unwrap_or_default(); renderer.with_layer(layout.bounds(), |renderer| { - element.draw( + overlay.draw( renderer, theme, style, @@ -109,7 +111,7 @@ where }); if let Some((mut nested, nested_layout)) = - element.overlay(layout, renderer).zip(nested_layout) + overlay.overlay(layout, renderer).zip(nested_layout) { recurse( &mut nested, @@ -144,10 +146,12 @@ where let mut layouts = layout.children(); if let Some(layout) = layouts.next() { - element.operate(layout, renderer, operation); + let overlay = element.as_overlay_mut(); + + overlay.operate(layout, renderer, operation); if let Some((mut nested, nested_layout)) = - element.overlay(layout, renderer).zip(layouts.next()) + overlay.overlay(layout, renderer).zip(layouts.next()) { recurse(&mut nested, nested_layout, renderer, operation); } @@ -182,8 +186,10 @@ where let mut layouts = layout.children(); if let Some(layout) = layouts.next() { + let overlay = element.as_overlay_mut(); + let nested_is_over = if let Some((mut nested, nested_layout)) = - element.overlay(layout, renderer).zip(layouts.next()) + overlay.overlay(layout, renderer).zip(layouts.next()) { recurse( &mut nested, @@ -203,7 +209,7 @@ where || cursor .position() .map(|cursor_position| { - element.is_over( + overlay.is_over( layout, renderer, cursor_position, @@ -211,7 +217,7 @@ where }) .unwrap_or_default(); - element.update( + overlay.update( event, layout, if nested_is_over { @@ -266,13 +272,14 @@ where let layout = layouts.next()?; let cursor_position = cursor.position()?; + let overlay = element.as_overlay_mut(); - if !element.is_over(layout, renderer, cursor_position) { + if !overlay.is_over(layout, renderer, cursor_position) { return None; } Some( - element + overlay .overlay(layout, renderer) .zip(layouts.next()) .and_then(|(mut overlay, layout)| { @@ -285,7 +292,7 @@ where ) }) .unwrap_or_else(|| { - element.mouse_interaction( + overlay.mouse_interaction( layout, cursor, viewport, renderer, ) }), @@ -315,12 +322,14 @@ where let mut layouts = layout.children(); if let Some(layout) = layouts.next() { - if element.is_over(layout, renderer, cursor_position) { + let overlay = element.as_overlay_mut(); + + if overlay.is_over(layout, renderer, cursor_position) { return true; } if let Some((mut nested, nested_layout)) = - element.overlay(layout, renderer).zip(layouts.next()) + overlay.overlay(layout, renderer).zip(layouts.next()) { recurse( &mut nested, diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index 9b396c69..88cab5e4 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -190,6 +190,7 @@ where let mut outdated = false; let mut redraw_request = window::RedrawRequest::Wait; let mut input_method = InputMethod::Disabled; + let viewport = Rectangle::with_size(self.bounds); let mut manual_overlay = ManuallyDrop::new( self.root @@ -198,6 +199,7 @@ where &mut self.state, Layout::new(&self.base), renderer, + &viewport, Vector::ZERO, ) .map(overlay::Nested::new), @@ -242,6 +244,7 @@ where &mut self.state, Layout::new(&self.base), renderer, + &viewport, Vector::ZERO, ) .map(overlay::Nested::new), @@ -443,6 +446,7 @@ where &mut self.state, Layout::new(&self.base), renderer, + &viewport, Vector::ZERO, ) .map(overlay::Nested::new) @@ -513,6 +517,7 @@ where &mut self.state, Layout::new(base), renderer, + &viewport, Vector::ZERO, ) .map(overlay::Nested::new) @@ -558,6 +563,8 @@ where renderer: &Renderer, operation: &mut dyn widget::Operation, ) { + let viewport = Rectangle::with_size(self.bounds); + self.root.as_widget().operate( &mut self.state, Layout::new(&self.base), @@ -572,6 +579,7 @@ where &mut self.state, Layout::new(&self.base), renderer, + &viewport, Vector::ZERO, ) .map(overlay::Nested::new) diff --git a/widget/src/button.rs b/widget/src/button.rs index d4500888..63987597 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -432,12 +432,14 @@ where tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { self.content.as_widget_mut().overlay( &mut tree.children[0], layout.children().next().unwrap(), renderer, + viewport, translation, ) } diff --git a/widget/src/column.rs b/widget/src/column.rs index 7200690b..777eb328 100644 --- a/widget/src/column.rs +++ b/widget/src/column.rs @@ -338,6 +338,7 @@ where tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { overlay::from_children( @@ -345,6 +346,7 @@ where tree, layout, renderer, + viewport, translation, ) } diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index f71e4a6e..195fe0cb 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -832,6 +832,7 @@ where tree: &'b mut widget::Tree, layout: Layout<'_>, _renderer: &Renderer, + _viewport: &Rectangle, translation: Vector, ) -> Option> { let is_focused = { diff --git a/widget/src/container.rs b/widget/src/container.rs index 82774186..1c774ced 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -379,12 +379,14 @@ where tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { self.content.as_widget_mut().overlay( tree, layout.children().next().unwrap(), renderer, + viewport, translation, ) } diff --git a/widget/src/grid.rs b/widget/src/grid.rs index da827007..76de415d 100644 --- a/widget/src/grid.rs +++ b/widget/src/grid.rs @@ -345,6 +345,7 @@ where tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { overlay::from_children( @@ -352,6 +353,7 @@ where tree, layout, renderer, + viewport, translation, ) } diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 1bd9f8ee..82908f88 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -694,6 +694,7 @@ where state: &'b mut core::widget::Tree, layout: core::Layout<'_>, renderer: &Renderer, + viewport: &Rectangle, translation: core::Vector, ) -> Option> { @@ -701,6 +702,7 @@ where state, layout, renderer, + viewport, translation, ) } @@ -948,6 +950,7 @@ where tree: &'b mut core::widget::Tree, layout: core::Layout<'_>, renderer: &Renderer, + viewport: &Rectangle, translation: core::Vector, ) -> Option> { @@ -959,6 +962,7 @@ where tree, layout, renderer, + viewport, translation, ) }); @@ -1844,7 +1848,12 @@ where /// ``` /// #[cfg(feature = "image")] -pub fn image(handle: impl Into) -> crate::Image { +pub fn image<'a, Handle, Theme>( + handle: impl Into, +) -> crate::Image<'a, Handle, Theme> +where + Theme: crate::image::Catalog, +{ crate::Image::new(handle.into()) } diff --git a/widget/src/image.rs b/widget/src/image.rs index 6c84ec92..fa40f441 100644 --- a/widget/src/image.rs +++ b/widget/src/image.rs @@ -19,14 +19,17 @@ pub mod viewer; pub use viewer::Viewer; +use crate::core; +use crate::core::border; use crate::core::image; use crate::core::layout; use crate::core::mouse; +use crate::core::overlay; use crate::core::renderer; use crate::core::widget::Tree; use crate::core::{ - ContentFit, Element, Layout, Length, Point, Rectangle, Rotation, Size, - Vector, Widget, + ContentFit, Element, Layout, Length, Point, Rectangle, Rotation, Shadow, + Size, Vector, Widget, }; pub use image::{FilterMethod, Handle}; @@ -54,8 +57,11 @@ pub fn viewer(handle: Handle) -> Viewer { /// } /// ``` /// -#[derive(Debug)] -pub struct Image { +#[allow(missing_debug_implementations)] +pub struct Image<'a, Handle = image::Handle, Theme = crate::Theme> +where + Theme: Catalog, +{ handle: Handle, width: Length, height: Length, @@ -64,9 +70,14 @@ pub struct Image { rotation: Rotation, opacity: f32, scale: f32, + translate: Option Vector + 'a>>, + class: Theme::Class<'a>, } -impl Image { +impl<'a, Handle, Theme> Image<'a, Handle, Theme> +where + Theme: Catalog, +{ /// Creates a new [`Image`] with the given path. pub fn new(handle: impl Into) -> Self { Image { @@ -78,6 +89,8 @@ impl Image { rotation: Rotation::default(), opacity: 1.0, scale: 1.0, + translate: None, + class: Theme::default(), } } @@ -130,6 +143,40 @@ impl Image { self.scale = scale.into(); self } + + /// Sets the translation that should be applied to an [`Image`], potentially making it + /// float above other content. + /// + /// This method takes a closure that will receive the non-scaled bounds of the [`Image`] + /// and the bounds of the viewport. The closure must produce a [`Vector`] representing + /// the translation to be applied. + /// + /// Translating can be useful to ensure images stay visible inside the viewport. + pub fn translate( + mut self, + translate: impl Fn(Rectangle, Rectangle) -> Vector + 'a, + ) -> Self { + self.translate = Some(Box::new(translate)); + self + } + + /// Sets the style of the [`Image`]. + #[must_use] + pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self + where + Theme::Class<'a>: From>, + { + self.class = (Box::new(style) as StyleFn<'a, Theme>).into(); + self + } + + /// Sets the style class of the [`Image`]. + #[cfg(feature = "advanced")] + #[must_use] + pub fn class(mut self, class: impl Into>) -> Self { + self.class = class.into(); + self + } } /// Computes the layout of an [`Image`]. @@ -174,26 +221,20 @@ where layout::Node::new(final_size) } -/// Draws an [`Image`] -pub fn draw( - renderer: &mut Renderer, - layout: Layout<'_>, - viewport: &Rectangle, +fn drawing_bounds( + renderer: &Renderer, + bounds: Rectangle, handle: &Handle, content_fit: ContentFit, - filter_method: FilterMethod, rotation: Rotation, - opacity: f32, scale: f32, -) where +) -> Rectangle +where Renderer: image::Renderer, - 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(rotated_size, bounds.size()); let fit_scale = Vector::new( @@ -214,36 +255,105 @@ pub fn draw( ), }; - let drawing_bounds = Rectangle::new(position, final_size); + Rectangle::new(position, final_size) +} - let render = |renderer: &mut Renderer| { - renderer.draw_image( - image::Image { - handle: handle.clone(), - filter_method, - rotation: rotation.radians(), - opacity, - snap: true, - }, - drawing_bounds, - ); - }; +fn must_clip(bounds: Rectangle, drawing_bounds: Rectangle) -> bool { + drawing_bounds.width > bounds.width || drawing_bounds.height > bounds.height +} + +/// Draws an [`Image`] +pub fn draw( + renderer: &mut Renderer, + layout: Layout<'_>, + viewport: &Rectangle, + handle: &Handle, + content_fit: ContentFit, + filter_method: FilterMethod, + rotation: Rotation, + opacity: f32, + scale: f32, + translate: Option<&dyn Fn(Rectangle, Rectangle) -> Vector>, + style: Style, +) where + Renderer: image::Renderer, + Handle: Clone, +{ + let bounds = layout.bounds(); + let drawing_bounds = + drawing_bounds(renderer, bounds, handle, content_fit, rotation, scale); + + if must_clip(bounds, drawing_bounds) { + if translate.is_some_and(|translate| { + scale > 1.0 || translate(bounds, *viewport) != Vector::ZERO + }) { + return; + } - if adjusted_fit.width > bounds.width || adjusted_fit.height > bounds.height - { if let Some(bounds) = bounds.intersection(viewport) { - renderer.with_layer(bounds, render); + renderer.with_layer(bounds, |renderer| { + render( + renderer, + handle, + filter_method, + rotation, + opacity, + drawing_bounds, + ); + }); } } else { - render(renderer); + render( + renderer, + handle, + filter_method, + rotation, + opacity, + drawing_bounds, + ); + } + + if style.shadow.color.a > 0.0 { + renderer.fill_quad( + renderer::Quad { + bounds: bounds.shrink(1.0), + shadow: style.shadow, + border: border::rounded(style.shadow_border_radius), + }, + style.shadow.color, + ); } } +fn render( + renderer: &mut Renderer, + handle: &Handle, + filter_method: FilterMethod, + rotation: Rotation, + opacity: f32, + drawing_bounds: Rectangle, +) where + Renderer: image::Renderer, + Handle: Clone, +{ + renderer.draw_image( + image::Image { + handle: handle.clone(), + filter_method, + rotation: rotation.radians(), + opacity, + snap: true, + }, + drawing_bounds, + ); +} + impl Widget - for Image + for Image<'_, Handle, Theme> where Renderer: image::Renderer, Handle: Clone, + Theme: Catalog, { fn size(&self) -> Size { Size { @@ -273,7 +383,7 @@ where &self, _state: &Tree, renderer: &mut Renderer, - _theme: &Theme, + theme: &Theme, _style: &renderer::Style, layout: Layout<'_>, _cursor: mouse::Cursor, @@ -289,17 +399,183 @@ where self.rotation, self.opacity, self.scale, + self.translate.as_deref(), + theme.style(&self.class), ); } + + fn overlay<'a>( + &'a mut self, + _state: &'a mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + viewport: &Rectangle, + translation: Vector, + ) -> Option> { + let translate = self.translate.as_ref()?; + let bounds = layout.bounds() + translation; + let drawing_bounds = drawing_bounds( + renderer, + bounds, + &self.handle, + self.content_fit, + self.rotation, + self.scale, + ); + + if must_clip(bounds, drawing_bounds) { + let translate = translate(bounds, *viewport); + + if self.scale <= 1.0 && translate == Vector::ZERO { + return None; + } + + Some(overlay::Element::new(Box::new(Overlay { + image: self, + viewport: *viewport, + clip_bounds: bounds + translate, + drawing_bounds: drawing_bounds + translate, + }))) + } else { + None + } + } } -impl<'a, Message, Theme, Renderer, Handle> From> +impl<'a, Message, Theme, Renderer, Handle> From> for Element<'a, Message, Theme, Renderer> where Renderer: image::Renderer, Handle: Clone + 'a, + Theme: Catalog + 'a, { - fn from(image: Image) -> Element<'a, Message, Theme, Renderer> { + fn from( + image: Image<'a, Handle, Theme>, + ) -> Element<'a, Message, Theme, Renderer> { Element::new(image) } } + +/// The theme catalog of an [`Image`]. +/// +/// All themes that can be used with [`Image`] +/// must implement this trait. +pub trait Catalog { + /// The item class of the [`Catalog`]. + type Class<'a>; + + /// The default class produced by the [`Catalog`]. + fn default<'a>() -> Self::Class<'a>; + + /// The [`Style`] of a class with the given status. + fn style(&self, class: &Self::Class<'_>) -> Style; +} + +/// A styling function for an [`Image`]. +pub type StyleFn<'a, Theme> = Box Style + 'a>; + +impl Catalog for crate::Theme { + type Class<'a> = StyleFn<'a, Self>; + + fn default<'a>() -> Self::Class<'a> { + Box::new(|_| Style::default()) + } + + fn style(&self, class: &Self::Class<'_>) -> Style { + class(self) + } +} + +/// The style of an [`Image`]. +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub struct Style { + /// The [`Shadow`] of the [`Image`]. + pub shadow: Shadow, + /// The border radius of the shadow. + pub shadow_border_radius: border::Radius, +} + +struct Overlay<'a, 'b, Handle, Theme> +where + Theme: Catalog, +{ + image: &'a Image<'b, Handle, Theme>, + viewport: Rectangle, + clip_bounds: Rectangle, + drawing_bounds: Rectangle, +} + +impl core::Overlay + for Overlay<'_, '_, Handle, Theme> +where + Renderer: image::Renderer, + Handle: Clone, + Theme: Catalog, +{ + fn layout(&mut self, _renderer: &Renderer, _bounds: Size) -> layout::Node { + layout::Node::new(self.clip_bounds.size()) + .move_to(self.clip_bounds.position()) + } + + fn is_over( + &self, + _layout: Layout<'_>, + _renderer: &Renderer, + _cursor_position: Point, + ) -> bool { + false + } + + fn draw( + &self, + renderer: &mut Renderer, + theme: &Theme, + _style: &renderer::Style, + layout: Layout<'_>, + _cursor: mouse::Cursor, + ) { + let bounds = layout.bounds(); + let clip_bounds = bounds.zoom(self.image.scale); + + let Some(clip_bounds) = clip_bounds.intersection(&self.viewport) else { + return; + }; + + let style = theme.style(&self.image.class); + + if style.shadow.color.a > 0.0 { + renderer.with_layer( + clip_bounds.expand(style.shadow.blur_radius), + |renderer| { + renderer.fill_quad( + renderer::Quad { + bounds: self + .drawing_bounds + .intersection(&clip_bounds) + .unwrap_or(self.drawing_bounds) + .shrink(1.0), + shadow: style.shadow, + border: border::rounded(style.shadow_border_radius), + }, + style.shadow.color, + ); + }, + ); + } + + renderer.with_layer(clip_bounds, |renderer| { + render( + renderer, + &self.image.handle, + self.image.filter_method, + self.image.rotation, + self.image.opacity, + self.drawing_bounds, + ); + }); + } + + fn index(&self) -> f32 { + self.image.scale * 0.5 + } +} diff --git a/widget/src/keyed/column.rs b/widget/src/keyed/column.rs index 3064a8c4..ed9b5dbf 100644 --- a/widget/src/keyed/column.rs +++ b/widget/src/keyed/column.rs @@ -369,6 +369,7 @@ where tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { overlay::from_children( @@ -376,6 +377,7 @@ where tree, layout, renderer, + viewport, translation, ) } diff --git a/widget/src/lazy.rs b/widget/src/lazy.rs index 8b7b38ce..82fab287 100644 --- a/widget/src/lazy.rs +++ b/widget/src/lazy.rs @@ -267,6 +267,7 @@ where tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { let overlay = InnerBuilder { @@ -283,7 +284,7 @@ where overlay_builder: |element, tree| { element .as_widget_mut() - .overlay(tree, layout, renderer, translation) + .overlay(tree, layout, renderer, viewport, translation) .map(|overlay| RefCell::new(Nested::new(overlay))) }, } diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index 0cfcc953..d6984109 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -447,6 +447,7 @@ where tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { self.rebuild_element_if_necessary(); @@ -469,6 +470,7 @@ where &mut tree.children[0], layout, renderer, + viewport, translation, ) .map(|overlay| RefCell::new(Nested::new(overlay))) diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs index e7c937af..ca5825e3 100644 --- a/widget/src/lazy/responsive.rs +++ b/widget/src/lazy/responsive.rs @@ -283,6 +283,7 @@ where tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { use std::ops::DerefMut; @@ -315,7 +316,13 @@ where ( element .as_widget_mut() - .overlay(tree, content_layout, renderer, translation) + .overlay( + tree, + content_layout, + renderer, + viewport, + translation, + ) .map(|overlay| RefCell::new(Nested::new(overlay))), content_layout_node, ) diff --git a/widget/src/mouse_area.rs b/widget/src/mouse_area.rs index 2ea0b059..54387e2d 100644 --- a/widget/src/mouse_area.rs +++ b/widget/src/mouse_area.rs @@ -296,12 +296,14 @@ where tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { self.content.as_widget_mut().overlay( &mut tree.children[0], layout, renderer, + viewport, translation, ) } diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index db93c724..743eec0a 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -979,6 +979,7 @@ where tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { let children = self @@ -997,7 +998,7 @@ where return None; } - content.overlay(state, layout, renderer, translation) + content.overlay(state, layout, renderer, viewport, translation) }) .collect::>(); diff --git a/widget/src/pane_grid/content.rs b/widget/src/pane_grid/content.rs index 4d63dd18..8a235be7 100644 --- a/widget/src/pane_grid/content.rs +++ b/widget/src/pane_grid/content.rs @@ -366,6 +366,7 @@ where tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { if let Some(title_bar) = self.title_bar.as_mut() { @@ -380,6 +381,7 @@ where title_bar_state, title_bar_layout, renderer, + viewport, translation, ) { Some(overlay) => Some(overlay), @@ -387,6 +389,7 @@ where body_state, children.next()?, renderer, + viewport, translation, ), } @@ -395,6 +398,7 @@ where &mut tree.children[0], layout, renderer, + viewport, translation, ) } diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs index 611c3d67..714f7f47 100644 --- a/widget/src/pane_grid/title_bar.rs +++ b/widget/src/pane_grid/title_bar.rs @@ -570,6 +570,7 @@ where tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { let mut children = layout.children(); @@ -588,7 +589,7 @@ where content .as_widget_mut() - .overlay(title_state, title_layout, renderer, translation) + .overlay(title_state, title_layout, renderer, viewport, translation) .or_else(move || { controls.as_mut().and_then(|controls| { let controls_layout = children.next()?; @@ -605,6 +606,7 @@ where compact_state, compact_layout, renderer, + viewport, translation, ) } else { @@ -612,6 +614,7 @@ where controls_state, controls_layout, renderer, + viewport, translation, ) } @@ -620,6 +623,7 @@ where controls_state, controls_layout, renderer, + viewport, translation, ) } diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 653c2f04..4a9d1e2f 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -690,6 +690,7 @@ where tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, + _viewport: &Rectangle, translation: Vector, ) -> Option> { let state = tree.state.downcast_mut::>(); diff --git a/widget/src/pin.rs b/widget/src/pin.rs index afa29398..2b663d1e 100644 --- a/widget/src/pin.rs +++ b/widget/src/pin.rs @@ -244,12 +244,14 @@ where tree: &'b mut widget::Tree, layout: Layout<'_>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { self.content.as_widget_mut().overlay( tree, layout.children().next().unwrap(), renderer, + viewport, translation, ) } diff --git a/widget/src/pop.rs b/widget/src/pop.rs index 5add1525..75973e3c 100644 --- a/widget/src/pop.rs +++ b/widget/src/pop.rs @@ -295,12 +295,14 @@ where tree: &'b mut Tree, layout: core::Layout<'_>, renderer: &Renderer, + viewport: &Rectangle, translation: core::Vector, ) -> Option> { self.content.as_widget_mut().overlay( &mut tree.children[0], layout, renderer, + viewport, translation, ) } diff --git a/widget/src/row.rs b/widget/src/row.rs index b9fd2569..1de29e17 100644 --- a/widget/src/row.rs +++ b/widget/src/row.rs @@ -337,6 +337,7 @@ where tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { overlay::from_children( @@ -344,6 +345,7 @@ where tree, layout, renderer, + viewport, translation, ) } @@ -546,9 +548,11 @@ where tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { - self.row.overlay(tree, layout, renderer, translation) + self.row + .overlay(tree, layout, renderer, viewport, translation) } } diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index d50591a1..9d704d40 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -1180,11 +1180,13 @@ where tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { let bounds = layout.bounds(); let content_layout = layout.children().next().unwrap(); let content_bounds = content_layout.bounds(); + let visible_bounds = bounds.intersection(viewport).unwrap_or(*viewport); let offset = tree.state.downcast_ref::().translation( self.direction, @@ -1196,6 +1198,7 @@ where &mut tree.children[0], layout.children().next().unwrap(), renderer, + &visible_bounds, translation - offset, ) } diff --git a/widget/src/stack.rs b/widget/src/stack.rs index df9f6162..dda9c357 100644 --- a/widget/src/stack.rs +++ b/widget/src/stack.rs @@ -354,6 +354,7 @@ where tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { overlay::from_children( @@ -361,6 +362,7 @@ where tree, layout, renderer, + viewport, translation, ) } diff --git a/widget/src/themer.rs b/widget/src/themer.rs index cf0845be..3d58db61 100644 --- a/widget/src/themer.rs +++ b/widget/src/themer.rs @@ -180,6 +180,7 @@ where tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { struct Overlay<'a, Message, Theme, NewTheme, Renderer> { @@ -198,7 +199,7 @@ where renderer: &Renderer, bounds: Size, ) -> layout::Node { - self.content.layout(renderer, bounds) + self.content.as_overlay_mut().layout(renderer, bounds) } fn draw( @@ -209,7 +210,7 @@ where layout: Layout<'_>, cursor: mouse::Cursor, ) { - self.content.draw( + self.content.as_overlay().draw( renderer, &(self.to_theme)(theme), style, @@ -228,6 +229,7 @@ where shell: &mut Shell<'_, Message>, ) { self.content + .as_overlay_mut() .update(event, layout, cursor, renderer, clipboard, shell); } @@ -237,7 +239,9 @@ where renderer: &Renderer, operation: &mut dyn Operation, ) { - self.content.operate(layout, renderer, operation); + self.content + .as_overlay_mut() + .operate(layout, renderer, operation); } fn mouse_interaction( @@ -248,6 +252,7 @@ where renderer: &Renderer, ) -> mouse::Interaction { self.content + .as_overlay() .mouse_interaction(layout, cursor, viewport, renderer) } @@ -257,7 +262,11 @@ where renderer: &Renderer, cursor_position: Point, ) -> bool { - self.content.is_over(layout, renderer, cursor_position) + self.content.as_overlay().is_over( + layout, + renderer, + cursor_position, + ) } fn overlay<'b>( @@ -267,6 +276,7 @@ where ) -> Option> { self.content + .as_overlay_mut() .overlay(layout, renderer) .map(|content| Overlay { to_theme: &self.to_theme, @@ -278,7 +288,7 @@ where self.content .as_widget_mut() - .overlay(tree, layout, renderer, translation) + .overlay(tree, layout, renderer, viewport, translation) .map(|content| Overlay { to_theme: &self.to_theme, content, diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index ce34f2a5..4223c8b6 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -273,6 +273,7 @@ where tree: &'b mut widget::Tree, layout: Layout<'_>, renderer: &Renderer, + viewport: &Rectangle, translation: Vector, ) -> Option> { let state = tree.state.downcast_ref::(); @@ -283,6 +284,7 @@ where children.next().unwrap(), layout, renderer, + viewport, translation, );