#![allow(non_snake_case)]
use std::rc::Rc;
use std::sync::atomic::{AtomicU64, Ordering};
use repose_core::*;
use repose_ui::overlay::OverlayHandle;
use repose_ui::{Box, Column, Row, Spacer, Text, ViewExt, ZStack};
use web_time::Duration;
use super::{AlertDialogDefaults, Button, ButtonConfig, TextButton};
use super::{DatePicker, DatePickerConfig, DatePickerState};
use super::{TimePicker, TimePickerConfig, TimePickerState};
static DIALOG_COUNTER: AtomicU64 = AtomicU64::new(0);
pub struct DialogState {
visible: Signal<bool>,
id: u64,
}
impl Default for DialogState {
fn default() -> Self {
Self::new()
}
}
impl DialogState {
pub fn new() -> Self {
Self {
visible: signal(false),
id: DIALOG_COUNTER.fetch_add(1, Ordering::Relaxed),
}
}
pub fn key(&self, suffix: &str) -> String {
format!("dlg_{}_{}", self.id, suffix)
}
pub fn is_visible(&self) -> bool {
self.visible.get()
}
pub fn show(&self) {
self.visible.set(true);
}
pub fn dismiss(&self) {
self.visible.set(false);
}
}
#[derive(Clone)]
pub struct DialogProperties {
pub on_dismiss_request: Option<Rc<dyn Fn()>>,
pub dismiss_on_click_outside: bool,
pub dismiss_on_back_press: bool,
}
impl Default for DialogProperties {
fn default() -> Self {
Self {
on_dismiss_request: None,
dismiss_on_click_outside: true,
dismiss_on_back_press: true,
}
}
}
pub fn Dialog(
state: Rc<DialogState>,
overlay: OverlayHandle,
modifier: Modifier,
properties: DialogProperties,
content: View,
) -> View {
let overlay_id = remember_with_key(state.key("oid"), || signal(0u64));
let current_content = remember_state_with_key(state.key("c"), || Box(Modifier::new()));
*current_content.borrow_mut() = content;
let props = remember_state_with_key(state.key("p"), || properties.clone());
*props.borrow_mut() = properties;
let spec = AnimationSpec::tween(Duration::from_millis(200), Easing::FastOutSlowIn);
let anim = remember_state_with_key(state.key("anim"), || AnimatedValue::new(0.0, spec));
let last_target = remember_state_with_key(state.key("atarget"), || f32::NAN);
let anim_target = if state.is_visible() { 1.0 } else { 0.0 };
{
let mut a = anim.borrow_mut();
let mut lt = last_target.borrow_mut();
if lt.is_nan() || (*lt - anim_target).abs() > 1e-6 {
a.set_spec(spec);
a.set_target(anim_target);
*lt = anim_target;
}
drop(lt);
if a.update() {
request_frame();
}
}
let progress = *anim.borrow().get();
let visible = state.is_visible() || progress > 0.01;
if visible {
if overlay_id.get() == 0 {
let builder: Rc<dyn Fn() -> View> = Rc::new({
let state = state.clone();
let anim = anim.clone();
let modifier = modifier.clone();
let current_content = current_content.clone();
let props = props.clone();
move || {
let progress = *anim.borrow().get();
let alpha = progress.min(1.0);
let th = theme();
let content = current_content.borrow().clone();
let p = props.borrow().clone();
let dialog = Box(Modifier::new()
.min_width(280.0)
.max_width(560.0)
.then(modifier.clone())
.justify_content(JustifyContent::CENTER)
.background(th.surface_container_high)
.clip_rounded(th.shapes.extra_large)
.alpha(alpha)
.focus_group()
.clickable()
.focusable(false)
.on_key_event({
let s = state.clone();
let p = props.clone();
move |ke| {
use repose_core::input::{Key, KeyEventType};
if ke.key == Key::Escape && ke.event_type == KeyEventType::Down {
let (dismiss, cb) = {
let p = p.borrow();
(p.dismiss_on_back_press, p.on_dismiss_request.clone())
};
if dismiss {
if let Some(cb) = cb {
cb();
} else {
s.dismiss();
}
return true;
}
}
false
}
}))
.child(content);
let scrim = Box(Modifier::new()
.fill_max_size()
.background(th.scrim.with_alpha((85.0 * alpha) as u8))
.focusable(false)
.on_scroll(|_| Vec2::default())
.on_click({
let s = state.clone();
let p = props.clone();
move || {
let (dismiss, cb) = {
let p = p.borrow();
(p.dismiss_on_click_outside, p.on_dismiss_request.clone())
};
if dismiss {
if let Some(cb) = cb {
cb();
} else {
s.dismiss();
}
}
}
}));
ZStack(Modifier::new().fill_max_size().absolute()).child((
scrim,
Box(Modifier::new()
.fill_max_size()
.justify_content(JustifyContent::CENTER)
.align_items(AlignItems::CENTER)
.hit_passthrough())
.child(dialog),
))
}
});
let id = overlay.show_entry(builder, 900.0, false);
overlay_id.set(id);
}
} else {
let prev = overlay_id.get();
if prev != 0 {
let _ = overlay.dismiss(prev);
overlay_id.set(0);
}
}
Box(Modifier::new())
}
#[derive(Clone, Debug)]
pub struct AlertDialogConfig {
pub modifier: Modifier,
pub scrim_color: Color,
pub min_width: f32,
pub max_width: f32,
pub horizontal_padding: f32,
pub shape_radius: Option<f32>,
pub container_color: Color,
pub tonal_elevation: f32,
}
impl Default for AlertDialogConfig {
fn default() -> Self {
Self {
modifier: Modifier::new(),
scrim_color: AlertDialogDefaults::scrim_color(),
min_width: AlertDialogDefaults::MIN_WIDTH,
max_width: AlertDialogDefaults::MAX_WIDTH,
horizontal_padding: AlertDialogDefaults::HORIZONTAL_PADDING,
shape_radius: None,
container_color: theme().surface_container_high,
tonal_elevation: 0.0,
}
}
}
pub fn AlertDialog(
state: Rc<DialogState>,
overlay: OverlayHandle,
title: View,
text: View,
confirm_button: View,
dismiss_button: Option<View>,
config: AlertDialogConfig,
) -> View {
let content = Box(Modifier::new()
.background(config.container_color)
.clip_rounded(
config
.shape_radius
.unwrap_or_else(|| theme().shapes.extra_large),
))
.child(super::alert_dialog_body(
title,
text,
confirm_button,
dismiss_button,
));
Dialog(
state,
overlay,
Modifier::new()
.min_width(config.min_width)
.max_width(config.max_width)
.then(config.modifier),
DialogProperties::default(),
content,
)
}
#[derive(Clone)]
pub struct DatePickerDialogConfig {
pub modifier: Modifier,
pub shape_radius: Option<f32>,
pub colors: super::DatePickerColors,
}
impl Default for DatePickerDialogConfig {
fn default() -> Self {
Self {
modifier: Modifier::new(),
shape_radius: None,
colors: super::DatePickerColors::default(),
}
}
}
pub fn DatePickerDialog(
state: Rc<DialogState>,
overlay: OverlayHandle,
picker_state: Rc<DatePickerState>,
on_confirm: Rc<dyn Fn(i32, u32, u32)>,
on_dismiss: Rc<dyn Fn()>,
config: DatePickerDialogConfig,
) -> View {
let content = Box(Modifier::new()
.background(config.colors.container_color)
.clip_rounded(
config
.shape_radius
.unwrap_or_else(|| theme().shapes.extra_large),
))
.child(Column(Modifier::new()).child((DatePicker(
picker_state.clone(),
on_confirm,
on_dismiss,
DatePickerConfig {
colors: config.colors,
..DatePickerConfig::default()
},
),)));
Dialog(
state,
overlay,
config.modifier,
DialogProperties::default(),
content,
)
}
#[derive(Clone)]
pub struct TimePickerDialogConfig {
pub modifier: Modifier,
pub shape_radius: Option<f32>,
pub container_color: Color,
pub colors: super::TimePickerColors,
}
impl Default for TimePickerDialogConfig {
fn default() -> Self {
Self {
modifier: Modifier::new(),
shape_radius: None,
container_color: theme().surface_container_high,
colors: super::TimePickerColors::default(),
}
}
}
pub fn TimePickerDialog(
state: Rc<DialogState>,
overlay: OverlayHandle,
picker_state: Rc<TimePickerState>,
on_confirm: Rc<dyn Fn(u32, u32)>,
on_dismiss: Rc<dyn Fn()>,
config: TimePickerDialogConfig,
) -> View {
let content = Box(Modifier::new()
.background(config.container_color)
.clip_rounded(
config
.shape_radius
.unwrap_or_else(|| theme().shapes.extra_large),
))
.child(Column(Modifier::new()).child((TimePicker(
picker_state.clone(),
on_confirm,
on_dismiss,
TimePickerConfig {
colors: config.colors,
..TimePickerConfig::default()
},
),)));
Dialog(
state,
overlay,
config.modifier,
DialogProperties::default(),
content,
)
}