use crate::overlay::OverlayDismissCallback;
use crate::widget_id::WidgetId;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ModalPresentation {
#[default]
Auto,
InTree,
NativeWindow,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ModalCloseBehavior {
ClickOutside,
EscapeKey,
#[default]
EscapeOrClickOutside,
Manual,
}
pub type ModalBuilder = Box<dyn FnOnce(&mut crate::widget_tree::WidgetTree) -> WidgetId>;
pub enum ModalContent {
ExistingWidget(WidgetId),
Deferred(ModalBuilder),
}
impl std::fmt::Debug for ModalContent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ExistingWidget(id) => f.debug_tuple("ExistingWidget").field(id).finish(),
Self::Deferred(_) => f.write_str("Deferred(..)"),
}
}
}
pub struct ModalRequest {
pub content: ModalContent,
pub presentation: ModalPresentation,
pub close_behavior: ModalCloseBehavior,
pub title: Option<String>,
pub size: Option<(u32, u32)>,
pub focus_target: Option<WidgetId>,
pub on_dismiss: Option<OverlayDismissCallback>,
}
impl ModalRequest {
pub fn in_tree(content_id: WidgetId) -> Self {
Self {
content: ModalContent::ExistingWidget(content_id),
presentation: ModalPresentation::Auto,
close_behavior: ModalCloseBehavior::default(),
title: None,
size: None,
focus_target: None,
on_dismiss: None,
}
}
pub fn deferred(
builder: impl FnOnce(&mut crate::widget_tree::WidgetTree) -> WidgetId + 'static,
) -> Self {
Self {
content: ModalContent::Deferred(Box::new(builder)),
presentation: ModalPresentation::Auto,
close_behavior: ModalCloseBehavior::default(),
title: None,
size: None,
focus_target: None,
on_dismiss: None,
}
}
pub fn presentation(mut self, presentation: ModalPresentation) -> Self {
self.presentation = presentation;
self
}
pub fn close_behavior(mut self, close_behavior: ModalCloseBehavior) -> Self {
self.close_behavior = close_behavior;
self
}
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
pub fn size(mut self, width: u32, height: u32) -> Self {
self.size = Some((width, height));
self
}
pub fn on_dismiss(mut self, callback: OverlayDismissCallback) -> Self {
self.on_dismiss = Some(callback);
self
}
pub fn focus_target(mut self, id: WidgetId) -> Self {
self.focus_target = Some(id);
self
}
}
impl std::fmt::Debug for ModalRequest {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ModalRequest")
.field("content", &self.content)
.field("presentation", &self.presentation)
.field("close_behavior", &self.close_behavior)
.field("title", &self.title)
.field("size", &self.size)
.field("focus_target", &self.focus_target)
.field(
"on_dismiss",
&self.on_dismiss.as_ref().map(|_| "<callback>"),
)
.finish()
}
}
#[derive(Debug)]
pub struct QueuedModalRequest {
pub source_widget: WidgetId,
pub request: ModalRequest,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_widgets::FillWidget;
#[test]
fn in_tree_request_defaults_to_auto() {
let mut tree = crate::WidgetTree::new();
let content_id = tree.add(FillWidget::new());
let request = ModalRequest::in_tree(content_id);
assert_eq!(request.presentation, ModalPresentation::Auto);
assert_eq!(
request.close_behavior,
ModalCloseBehavior::EscapeOrClickOutside
);
match request.content {
ModalContent::ExistingWidget(id) => assert_eq!(id, content_id),
ModalContent::Deferred(_) => panic!("expected ExistingWidget content"),
}
}
#[test]
fn deferred_request_can_override_metadata() {
let request =
ModalRequest::deferred(|tree| tree.add(crate::test_widgets::FillWidget::new()))
.presentation(ModalPresentation::NativeWindow)
.close_behavior(ModalCloseBehavior::Manual)
.title("Preferences")
.size(640, 480);
assert_eq!(request.presentation, ModalPresentation::NativeWindow);
assert_eq!(request.close_behavior, ModalCloseBehavior::Manual);
assert_eq!(request.title.as_deref(), Some("Preferences"));
assert_eq!(request.size, Some((640, 480)));
assert!(matches!(request.content, ModalContent::Deferred(_)));
}
#[test]
fn focus_target_defaults_to_none() {
let mut tree = crate::WidgetTree::new();
let content_id = tree.add(FillWidget::new());
let in_tree = ModalRequest::in_tree(content_id);
assert!(in_tree.focus_target.is_none());
let deferred =
ModalRequest::deferred(|tree| tree.add(crate::test_widgets::FillWidget::new()));
assert!(deferred.focus_target.is_none());
}
#[test]
fn focus_target_builder_sets_field() {
let mut tree = crate::WidgetTree::new();
let content_id = tree.add(FillWidget::new());
let target_id = tree.add(FillWidget::new());
let request = ModalRequest::in_tree(content_id).focus_target(target_id);
assert_eq!(request.focus_target, Some(target_id));
}
}