pub(crate) mod layout;
pub(crate) mod node;
pub(crate) mod reconcile;
pub use node::PopoverNode;
pub(crate) use reconcile::*;
use crate::callback::Callback;
use crate::core::element::{Element, ElementKind, IntoElement};
use crate::overlay::OverlayScope;
use crate::style::Length;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub enum PopoverPlacement {
#[default]
BelowStart,
BelowCenter,
BelowEnd,
AboveStart,
AboveCenter,
AboveEnd,
RightStart,
RightCenter,
RightEnd,
LeftStart,
LeftCenter,
LeftEnd,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct PopoverOffset {
pub x: i16,
pub y: i16,
}
impl PopoverOffset {
pub const ZERO: Self = Self { x: 0, y: 0 };
}
impl From<(i16, i16)> for PopoverOffset {
fn from(value: (i16, i16)) -> Self {
Self {
x: value.0,
y: value.1,
}
}
}
#[derive(Clone)]
pub struct Popover {
pub(crate) trigger: Box<Element>,
pub(crate) content: Box<Element>,
pub(crate) on_close: Option<Callback<()>>,
pub(crate) open: bool,
pub(crate) scope: OverlayScope,
pub(crate) placement: PopoverPlacement,
pub(crate) offset: PopoverOffset,
pub(crate) clamp: bool,
pub(crate) auto_flip: bool,
pub(crate) min_trigger_width: bool,
pub(crate) fit_trigger_width: bool,
pub(crate) max_width: Option<Length>,
pub(crate) anchor: Option<(u16, u16)>,
}
impl Default for Popover {
fn default() -> Self {
Self::new()
}
}
impl Popover {
pub fn new() -> Self {
Self {
trigger: Box::new(crate::widgets::Spacer::new().into()),
content: Box::new(crate::widgets::Spacer::new().into()),
on_close: None,
open: false,
scope: OverlayScope::RootPortal,
placement: PopoverPlacement::default(),
offset: PopoverOffset::ZERO,
clamp: true,
auto_flip: true,
min_trigger_width: true,
fit_trigger_width: false,
max_width: None,
anchor: None,
}
}
pub fn trigger(mut self, trigger: impl IntoElement) -> Self {
self.trigger = Box::new(trigger.into());
self
}
pub fn content(mut self, content: impl IntoElement) -> Self {
self.content = Box::new(content.into());
self
}
pub fn open(mut self, open: bool) -> Self {
self.open = open;
self
}
pub fn scope(mut self, scope: OverlayScope) -> Self {
self.scope = scope;
self
}
pub fn placement(mut self, placement: PopoverPlacement) -> Self {
self.placement = placement;
self
}
pub fn offset(mut self, offset: impl Into<PopoverOffset>) -> Self {
self.offset = offset.into();
self
}
pub fn clamp(mut self, clamp: bool) -> Self {
self.clamp = clamp;
self
}
pub fn auto_flip(mut self, auto_flip: bool) -> Self {
self.auto_flip = auto_flip;
self
}
pub fn min_trigger_width(mut self, min_trigger_width: bool) -> Self {
self.min_trigger_width = min_trigger_width;
self
}
pub fn fit_trigger_width(mut self, fit_trigger_width: bool) -> Self {
self.fit_trigger_width = fit_trigger_width;
self
}
pub fn max_width(mut self, max_width: Length) -> Self {
self.max_width = Some(max_width);
self
}
pub fn anchor(mut self, anchor: Option<(u16, u16)>) -> Self {
self.anchor = anchor;
self
}
pub fn on_close(mut self, cb: Callback<()>) -> Self {
self.on_close = Some(cb);
self
}
}
impl From<Popover> for Element {
fn from(popover: Popover) -> Self {
Element::new(ElementKind::Popover(popover)).with_layout(crate::style::LayoutConstraints {
min_w: crate::style::Length::Px(0),
min_h: crate::style::Length::Px(0),
..Default::default()
})
}
}
impl crate::layout::hash::LayoutHash for Popover {
fn layout_hash(
&self,
hasher: &mut impl std::hash::Hasher,
recurse: &dyn Fn(&Element) -> Option<u64>,
) -> Option<()> {
use std::hash::Hash;
self.open.hash(hasher);
self.scope.hash(hasher);
self.placement.hash(hasher);
self.offset.hash(hasher);
self.min_trigger_width.hash(hasher);
self.fit_trigger_width.hash(hasher);
self.max_width.hash(hasher);
self.anchor.hash(hasher);
recurse(self.trigger.as_ref())?.hash(hasher);
Some(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::element::ElementKind;
#[test]
fn popover_defaults_to_root_portal_scope() {
let element: Element = Popover::new().into();
let ElementKind::Popover(popover) = element.kind else {
panic!("expected popover element");
};
assert_eq!(popover.scope, OverlayScope::RootPortal);
}
#[test]
fn popover_scope_builder_updates_scope() {
let element: Element = Popover::new().scope(OverlayScope::Local).into();
let ElementKind::Popover(popover) = element.kind else {
panic!("expected popover element");
};
assert_eq!(popover.scope, OverlayScope::Local);
}
}