use crate::message::Message;
use iced::advanced::Shell;
use iced::advanced::layout::{self, Layout};
use iced::advanced::overlay;
use iced::advanced::renderer;
use iced::advanced::widget::{self, Widget};
use iced::{Element, Event, Length, Point, Rectangle, Size, Vector};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Position {
Below,
Above,
Left,
Right,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(crate) enum Align {
Start,
#[default]
Center,
End,
}
pub(crate) struct OverlayWrapper<'a> {
anchor: Element<'a, Message>,
content: Element<'a, Message>,
position: Position,
gap: f32,
offset_x: f32,
offset_y: f32,
flip: bool,
align: Align,
}
impl<'a> OverlayWrapper<'a> {
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
anchor: Element<'a, Message>,
content: Element<'a, Message>,
position: Position,
gap: f32,
offset_x: f32,
offset_y: f32,
flip: bool,
align: Align,
) -> Self {
Self {
anchor,
content,
position,
gap,
offset_x,
offset_y,
flip,
align,
}
}
}
impl Widget<Message, iced::Theme, iced::Renderer> for OverlayWrapper<'_> {
fn children(&self) -> Vec<widget::Tree> {
vec![
widget::Tree::new(&self.anchor),
widget::Tree::new(&self.content),
]
}
fn diff(&self, tree: &mut widget::Tree) {
tree.diff_children(&[self.anchor.as_widget(), self.content.as_widget()]);
}
fn size(&self) -> Size<Length> {
self.anchor.as_widget().size()
}
fn size_hint(&self) -> Size<Length> {
self.anchor.as_widget().size_hint()
}
fn layout(
&mut self,
tree: &mut widget::Tree,
renderer: &iced::Renderer,
limits: &layout::Limits,
) -> layout::Node {
self.anchor
.as_widget_mut()
.layout(&mut tree.children[0], renderer, limits)
}
fn draw(
&self,
tree: &widget::Tree,
renderer: &mut iced::Renderer,
theme: &iced::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor: iced::mouse::Cursor,
viewport: &Rectangle,
) {
self.anchor.as_widget().draw(
&tree.children[0],
renderer,
theme,
style,
layout,
cursor,
viewport,
);
}
fn update(
&mut self,
tree: &mut widget::Tree,
event: &Event,
layout: Layout<'_>,
cursor: iced::mouse::Cursor,
renderer: &iced::Renderer,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
) {
self.anchor.as_widget_mut().update(
&mut tree.children[0],
event,
layout,
cursor,
renderer,
shell,
viewport,
);
}
fn mouse_interaction(
&self,
tree: &widget::Tree,
layout: Layout<'_>,
cursor: iced::mouse::Cursor,
viewport: &Rectangle,
renderer: &iced::Renderer,
) -> iced::mouse::Interaction {
self.anchor.as_widget().mouse_interaction(
&tree.children[0],
layout,
cursor,
viewport,
renderer,
)
}
fn overlay<'b>(
&'b mut self,
tree: &'b mut widget::Tree,
layout: Layout<'b>,
renderer: &iced::Renderer,
viewport: &Rectangle,
translation: Vector,
) -> Option<overlay::Element<'b, Message, iced::Theme, iced::Renderer>> {
let mut children = tree.children.iter_mut();
let anchor_tree = children
.next()
.expect("OverlayWrapper must have anchor tree child");
let content_tree = children
.next()
.expect("OverlayWrapper must have content tree child");
let anchor_overlay = self.anchor.as_widget_mut().overlay(
anchor_tree,
layout,
renderer,
viewport,
translation,
);
let content_overlay = overlay::Element::new(Box::new(OverlayContent {
content: &mut self.content,
tree: content_tree,
position: self.position,
gap: self.gap,
offset_x: self.offset_x,
offset_y: self.offset_y,
flip: self.flip,
align: self.align,
anchor_bounds: layout.bounds(),
translation,
}));
Some(
overlay::Group::with_children(
anchor_overlay
.into_iter()
.chain(Some(content_overlay))
.collect(),
)
.overlay(),
)
}
fn operate(
&mut self,
tree: &mut widget::Tree,
layout: Layout<'_>,
renderer: &iced::Renderer,
operation: &mut dyn widget::Operation,
) {
self.anchor
.as_widget_mut()
.operate(&mut tree.children[0], layout, renderer, operation);
self.content
.as_widget_mut()
.operate(&mut tree.children[1], layout, renderer, operation);
}
}
impl<'a> From<OverlayWrapper<'a>> for Element<'a, Message> {
fn from(wrapper: OverlayWrapper<'a>) -> Self {
Element::new(wrapper)
}
}
struct OverlayContent<'a, 'b> {
content: &'b mut Element<'a, Message>,
tree: &'b mut widget::Tree,
position: Position,
gap: f32,
offset_x: f32,
offset_y: f32,
flip: bool,
align: Align,
anchor_bounds: Rectangle,
translation: Vector,
}
fn content_layout<'a>(layout: Layout<'a>) -> Layout<'a> {
layout
.children()
.next()
.expect("overlay content must have a child layout")
}
impl overlay::Overlay<Message, iced::Theme, iced::Renderer> for OverlayContent<'_, '_> {
fn layout(&mut self, renderer: &iced::Renderer, bounds: Size) -> layout::Node {
let limits = layout::Limits::new(Size::ZERO, bounds);
let content_layout = self
.content
.as_widget_mut()
.layout(self.tree, renderer, &limits);
let content_size = content_layout.bounds().size();
let anchor = Rectangle {
x: self.anchor_bounds.x + self.translation.x,
y: self.anchor_bounds.y + self.translation.y,
width: self.anchor_bounds.width,
height: self.anchor_bounds.height,
};
let position = if self.flip {
match self.position {
Position::Below => {
let below_y = anchor.y + anchor.height + self.gap;
if below_y + content_size.height > bounds.height {
let above_y = anchor.y - content_size.height - self.gap;
if above_y >= 0.0 {
Position::Above
} else {
Position::Below }
} else {
Position::Below
}
}
Position::Above => {
let above_y = anchor.y - content_size.height - self.gap;
if above_y < 0.0 {
let below_y = anchor.y + anchor.height + self.gap;
if below_y + content_size.height <= bounds.height {
Position::Below
} else {
Position::Above
}
} else {
Position::Above
}
}
Position::Left => {
let left_x = anchor.x - content_size.width - self.gap;
if left_x < 0.0 {
let right_x = anchor.x + anchor.width + self.gap;
if right_x + content_size.width <= bounds.width {
Position::Right
} else {
Position::Left
}
} else {
Position::Left
}
}
Position::Right => {
let right_x = anchor.x + anchor.width + self.gap;
if right_x + content_size.width > bounds.width {
let left_x = anchor.x - content_size.width - self.gap;
if left_x >= 0.0 {
Position::Left
} else {
Position::Right
}
} else {
Position::Right
}
}
}
} else {
self.position
};
let cross_h = match self.align {
Align::Start => anchor.x,
Align::Center => anchor.x + (anchor.width - content_size.width) / 2.0,
Align::End => anchor.x + anchor.width - content_size.width,
};
let cross_v = match self.align {
Align::Start => anchor.y,
Align::Center => anchor.y + (anchor.height - content_size.height) / 2.0,
Align::End => anchor.y + anchor.height - content_size.height,
};
let (x, y) = match position {
Position::Below => (cross_h, anchor.y + anchor.height + self.gap),
Position::Above => (cross_h, anchor.y - content_size.height - self.gap),
Position::Left => (anchor.x - content_size.width - self.gap, cross_v),
Position::Right => (anchor.x + anchor.width + self.gap, cross_v),
};
let final_x = (x + self.offset_x).clamp(0.0, (bounds.width - content_size.width).max(0.0));
let final_y =
(y + self.offset_y).clamp(0.0, (bounds.height - content_size.height).max(0.0));
layout::Node::with_children(content_size, vec![content_layout])
.move_to(Point::new(final_x, final_y))
}
fn draw(
&self,
renderer: &mut iced::Renderer,
theme: &iced::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor: iced::mouse::Cursor,
) {
let content_layout = content_layout(layout);
self.content.as_widget().draw(
self.tree,
renderer,
theme,
style,
content_layout,
cursor,
&Rectangle::with_size(Size::INFINITE),
);
}
fn update(
&mut self,
event: &Event,
layout: Layout<'_>,
cursor: iced::mouse::Cursor,
renderer: &iced::Renderer,
shell: &mut Shell<'_, Message>,
) {
let content_layout = content_layout(layout);
self.content.as_widget_mut().update(
self.tree,
event,
content_layout,
cursor,
renderer,
shell,
&Rectangle::with_size(Size::INFINITE),
);
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor: iced::mouse::Cursor,
renderer: &iced::Renderer,
) -> iced::mouse::Interaction {
let viewport = Rectangle::with_size(Size::INFINITE);
let content_layout = content_layout(layout);
self.content.as_widget().mouse_interaction(
self.tree,
content_layout,
cursor,
&viewport,
renderer,
)
}
fn operate(
&mut self,
layout: Layout<'_>,
renderer: &iced::Renderer,
operation: &mut dyn widget::Operation,
) {
let content_layout = content_layout(layout);
self.content
.as_widget_mut()
.operate(self.tree, content_layout, renderer, operation);
}
fn overlay<'c>(
&'c mut self,
layout: Layout<'c>,
renderer: &iced::Renderer,
) -> Option<overlay::Element<'c, Message, iced::Theme, iced::Renderer>> {
let content_layout = content_layout(layout);
self.content.as_widget_mut().overlay(
self.tree,
content_layout,
renderer,
&layout.bounds(),
Vector::ZERO,
)
}
}
#[cfg(test)]
mod tests {
fn clamp_position(
x: f32,
y: f32,
content_w: f32,
content_h: f32,
viewport_w: f32,
viewport_h: f32,
) -> (f32, f32) {
let final_x = x.clamp(0.0, (viewport_w - content_w).max(0.0));
let final_y = y.clamp(0.0, (viewport_h - content_h).max(0.0));
(final_x, final_y)
}
#[test]
fn clamp_within_viewport() {
let (x, y) = clamp_position(100.0, 100.0, 50.0, 50.0, 800.0, 600.0);
assert_eq!((x, y), (100.0, 100.0));
}
#[test]
fn clamp_right_edge() {
let (x, _) = clamp_position(780.0, 100.0, 50.0, 50.0, 800.0, 600.0);
assert_eq!(x, 750.0);
}
#[test]
fn clamp_bottom_edge() {
let (_, y) = clamp_position(100.0, 580.0, 50.0, 50.0, 800.0, 600.0);
assert_eq!(y, 550.0);
}
#[test]
fn clamp_negative() {
let (x, y) = clamp_position(-10.0, -20.0, 50.0, 50.0, 800.0, 600.0);
assert_eq!((x, y), (0.0, 0.0));
}
#[test]
fn clamp_content_larger_than_viewport() {
let (x, y) = clamp_position(100.0, 100.0, 900.0, 700.0, 800.0, 600.0);
assert_eq!((x, y), (0.0, 0.0));
}
#[test]
fn clamp_exact_fit() {
let (x, y) = clamp_position(50.0, 50.0, 800.0, 600.0, 800.0, 600.0);
assert_eq!((x, y), (0.0, 0.0));
}
#[test]
fn clamp_zero_size_content() {
let (x, y) = clamp_position(400.0, 300.0, 0.0, 0.0, 800.0, 600.0);
assert_eq!((x, y), (400.0, 300.0));
}
}