Make Overlay aware of viewport

... and unify `Image::float` and `Image::translate`
This commit is contained in:
Héctor Ramón Jiménez 2025-04-26 02:51:32 +02:00
parent a2a7c18c9d
commit 6c51a9579d
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
29 changed files with 121 additions and 56 deletions

View file

@ -368,12 +368,13 @@ where
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
viewport: &Rectangle,
translation: Vector,
) -> Option<overlay::Element<'b, B, Theme, Renderer>> {
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<overlay::Element<'b, Message, Theme, Renderer>> {
self.element
.widget
.overlay(state, layout, renderer, translation)
self.element.widget.overlay(
state,
layout,
renderer,
viewport,
translation,
)
}
}

View file

@ -122,6 +122,7 @@ pub fn from_children<'a, Message, Theme, Renderer>(
tree: &'a mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
viewport: &Rectangle,
translation: Vector,
) -> Option<Element<'a, Message, Theme, Renderer>>
where
@ -132,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::<Vec<_>>();

View file

@ -146,6 +146,7 @@ where
_state: &'a mut Tree,
_layout: Layout<'_>,
_renderer: &Renderer,
_viewport: &Rectangle,
_translation: Vector,
) -> Option<overlay::Element<'a, Message, Theme, Renderer>> {
None

View file

@ -212,11 +212,9 @@ fn card<'a>(
.scale(thumbnail.zoom.interpolate(1.0, 1.1, now))
.translate(move |bounds, viewport| {
let final_bounds = bounds.zoom(1.1);
final_bounds.offset(&viewport.shrink(10))
* thumbnail.zoom.interpolate(0.0, 1.0, now)
})
.float(true)
.style(move |_theme| image::Style {
shadow: Shadow {
color: Color::BLACK.scale_alpha(

View file

@ -425,6 +425,7 @@ mod toast {
state: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
viewport: &Rectangle,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
let instants = state.state.downcast_mut::<Vec<Option<Instant>>>();
@ -435,6 +436,7 @@ mod toast {
&mut content_state[0],
layout,
renderer,
viewport,
translation,
);

View file

@ -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)

View file

@ -432,12 +432,14 @@ where
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
viewport: &Rectangle,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
self.content.as_widget_mut().overlay(
&mut tree.children[0],
layout.children().next().unwrap(),
renderer,
viewport,
translation,
)
}

View file

@ -338,6 +338,7 @@ where
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
viewport: &Rectangle,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
overlay::from_children(
@ -345,6 +346,7 @@ where
tree,
layout,
renderer,
viewport,
translation,
)
}

View file

@ -832,6 +832,7 @@ where
tree: &'b mut widget::Tree,
layout: Layout<'_>,
_renderer: &Renderer,
_viewport: &Rectangle,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
let is_focused = {

View file

@ -379,12 +379,14 @@ where
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
viewport: &Rectangle,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
self.content.as_widget_mut().overlay(
tree,
layout.children().next().unwrap(),
renderer,
viewport,
translation,
)
}

View file

@ -345,6 +345,7 @@ where
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
viewport: &Rectangle,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
overlay::from_children(
@ -352,6 +353,7 @@ where
tree,
layout,
renderer,
viewport,
translation,
)
}

View file

@ -694,6 +694,7 @@ where
state: &'b mut core::widget::Tree,
layout: core::Layout<'_>,
renderer: &Renderer,
viewport: &Rectangle,
translation: core::Vector,
) -> Option<core::overlay::Element<'b, Message, Theme, Renderer>>
{
@ -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<core::overlay::Element<'b, Message, Theme, Renderer>>
{
@ -959,6 +962,7 @@ where
tree,
layout,
renderer,
viewport,
translation,
)
});

View file

@ -71,7 +71,6 @@ where
opacity: f32,
scale: f32,
translate: Option<Box<dyn Fn(Rectangle, Rectangle) -> Vector + 'a>>,
float: bool,
class: Theme::Class<'a>,
}
@ -91,7 +90,6 @@ where
opacity: 1.0,
scale: 1.0,
translate: None,
float: false,
class: Theme::default(),
}
}
@ -146,14 +144,14 @@ where
self
}
/// Sets the translation to apply to the [`Image`] when floating.
/// 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 floating images stay visible inside the
/// viewport.
/// Translating can be useful to ensure images stay visible inside the viewport.
pub fn translate(
mut self,
translate: impl Fn(Rectangle, Rectangle) -> Vector + 'a,
@ -162,18 +160,6 @@ where
self
}
/// Sets whether an [`Image`] should float above other content when
/// scaled up.
///
/// By default, an [`Image`] has this flag set to `false`; meaning it
/// will be clipped or "framed" inside its bounds.
///
/// Enabling this flag is useful to create cool hover effects!
pub fn float(mut self, float: bool) -> Self {
self.float = float;
self
}
/// Sets the style of the [`Image`].
#[must_use]
pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
@ -287,7 +273,7 @@ pub fn draw<Renderer, Handle>(
rotation: Rotation,
opacity: f32,
scale: f32,
float: bool,
translate: Option<&dyn Fn(Rectangle, Rectangle) -> Vector>,
style: Style,
) where
Renderer: image::Renderer<Handle = Handle>,
@ -298,7 +284,9 @@ pub fn draw<Renderer, Handle>(
drawing_bounds(renderer, bounds, handle, content_fit, rotation, scale);
if must_clip(bounds, drawing_bounds) {
if scale > 1.0 && float {
if translate.is_some_and(|translate| {
scale > 1.0 || translate(bounds, *viewport) != Vector::ZERO
}) {
return;
}
@ -411,7 +399,7 @@ where
self.rotation,
self.opacity,
self.scale,
self.float,
self.translate.as_deref(),
theme.style(&self.class),
);
}
@ -421,12 +409,10 @@ where
_state: &'a mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
viewport: &Rectangle,
translation: Vector,
) -> Option<overlay::Element<'a, Message, Theme, Renderer>> {
if !self.float || self.scale <= 1.0 {
return None;
}
let translate = self.translate.as_ref()?;
let bounds = layout.bounds() + translation;
let drawing_bounds = drawing_bounds(
renderer,
@ -438,10 +424,17 @@ where
);
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,
clip_bounds: bounds,
drawing_bounds,
viewport: *viewport,
clip_bounds: bounds + translate,
drawing_bounds: drawing_bounds + translate,
})))
} else {
None
@ -507,6 +500,7 @@ where
Theme: Catalog,
{
image: &'a Image<'b, Handle, Theme>,
viewport: Rectangle,
clip_bounds: Rectangle,
drawing_bounds: Rectangle,
}
@ -518,18 +512,9 @@ where
Handle: Clone,
Theme: Catalog,
{
fn layout(&mut self, _renderer: &Renderer, bounds: Size) -> layout::Node {
let bounds = if let Some(translate) = &self.image.translate {
self.clip_bounds
+ translate(
self.clip_bounds,
Rectangle::new(Point::ORIGIN, bounds),
)
} else {
self.clip_bounds
};
layout::Node::new(bounds.size()).move_to(bounds.position())
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(
@ -550,7 +535,6 @@ where
_cursor: mouse::Cursor,
) {
let bounds = layout.bounds();
let translation = bounds.position() - self.clip_bounds.position();
let clip_bounds = bounds.zoom(self.image.scale);
let style = theme.style(&self.image.class);
@ -560,7 +544,11 @@ where
|renderer| {
renderer.fill_quad(
renderer::Quad {
bounds: clip_bounds.shrink(1.0),
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),
},
@ -570,6 +558,10 @@ where
);
}
let clip_bounds = clip_bounds
.intersection(&self.viewport)
.unwrap_or(self.viewport);
renderer.with_layer(clip_bounds, |renderer| {
render(
renderer,
@ -577,7 +569,7 @@ where
self.image.filter_method,
self.image.rotation,
self.image.opacity,
self.drawing_bounds + translation,
self.drawing_bounds,
);
});
}

View file

@ -369,6 +369,7 @@ where
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
viewport: &Rectangle,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
overlay::from_children(
@ -376,6 +377,7 @@ where
tree,
layout,
renderer,
viewport,
translation,
)
}

View file

@ -267,6 +267,7 @@ where
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
viewport: &Rectangle,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
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)))
},
}

View file

@ -447,6 +447,7 @@ where
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
viewport: &Rectangle,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
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)))

View file

@ -283,6 +283,7 @@ where
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
viewport: &Rectangle,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
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,
)

View file

@ -296,12 +296,14 @@ where
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
viewport: &Rectangle,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
self.content.as_widget_mut().overlay(
&mut tree.children[0],
layout,
renderer,
viewport,
translation,
)
}

View file

@ -979,6 +979,7 @@ where
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
viewport: &Rectangle,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
let children = self
@ -997,7 +998,7 @@ where
return None;
}
content.overlay(state, layout, renderer, translation)
content.overlay(state, layout, renderer, viewport, translation)
})
.collect::<Vec<_>>();

View file

@ -366,6 +366,7 @@ where
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
viewport: &Rectangle,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
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,
)
}

View file

@ -570,6 +570,7 @@ where
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
viewport: &Rectangle,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
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,
)
}

View file

@ -690,6 +690,7 @@ where
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
_viewport: &Rectangle,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();

View file

@ -244,12 +244,14 @@ where
tree: &'b mut widget::Tree,
layout: Layout<'_>,
renderer: &Renderer,
viewport: &Rectangle,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
self.content.as_widget_mut().overlay(
tree,
layout.children().next().unwrap(),
renderer,
viewport,
translation,
)
}

View file

@ -295,12 +295,14 @@ where
tree: &'b mut Tree,
layout: core::Layout<'_>,
renderer: &Renderer,
viewport: &Rectangle,
translation: core::Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
self.content.as_widget_mut().overlay(
&mut tree.children[0],
layout,
renderer,
viewport,
translation,
)
}

View file

@ -337,6 +337,7 @@ where
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
viewport: &Rectangle,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
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<overlay::Element<'b, Message, Theme, Renderer>> {
self.row.overlay(tree, layout, renderer, translation)
self.row
.overlay(tree, layout, renderer, viewport, translation)
}
}

View file

@ -1180,11 +1180,13 @@ where
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
viewport: &Rectangle,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
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::<State>().translation(
self.direction,
@ -1196,6 +1198,7 @@ where
&mut tree.children[0],
layout.children().next().unwrap(),
renderer,
&visible_bounds,
translation - offset,
)
}

View file

@ -354,6 +354,7 @@ where
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
viewport: &Rectangle,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
overlay::from_children(
@ -361,6 +362,7 @@ where
tree,
layout,
renderer,
viewport,
translation,
)
}

View file

@ -180,6 +180,7 @@ where
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
viewport: &Rectangle,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
struct Overlay<'a, Message, Theme, NewTheme, Renderer> {
@ -287,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,

View file

@ -273,6 +273,7 @@ where
tree: &'b mut widget::Tree,
layout: Layout<'_>,
renderer: &Renderer,
viewport: &Rectangle,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
let state = tree.state.downcast_ref::<State>();
@ -283,6 +284,7 @@ where
children.next().unwrap(),
layout,
renderer,
viewport,
translation,
);