use std::cell::Cell;
use std::rc::Rc;
use crate::color::Color;
use crate::draw_ctx::DrawCtx;
use crate::event::{Event, EventResult, Key};
use crate::geometry::{Rect, Size};
use crate::layout_props::WidgetBase;
use crate::theme::current_visuals;
use crate::widget::{activate_action_at, cancel_action_path, default_action_path, Widget};
use crate::widgets::window::chrome::{paint_chrome_shadow, ChromeStyle};
const EDGE_MARGIN: f64 = 24.0;
const CORNER_R: f64 = 10.0;
pub struct ModalSheet {
bounds: Rect,
children: Vec<Box<dyn Widget>>, base: WidgetBase,
visible: Rc<Cell<bool>>,
panel_size: Size,
panel: Rect,
escape_closes: bool,
key_passthrough: bool,
on_close: Option<Box<dyn Fn()>>,
default_action: Option<Box<dyn Fn()>>,
cancel_action: Option<Box<dyn Fn()>>,
}
impl ModalSheet {
pub fn new(visible: Rc<Cell<bool>>, content: Box<dyn Widget>) -> Self {
Self {
bounds: Rect::default(),
children: vec![content],
base: WidgetBase::new(),
visible,
panel_size: Size::new(480.0, 360.0),
panel: Rect::default(),
escape_closes: true,
key_passthrough: false,
on_close: None,
default_action: None,
cancel_action: None,
}
}
pub fn with_key_passthrough(mut self, passthrough: bool) -> Self {
self.key_passthrough = passthrough;
self
}
pub fn with_panel_size(mut self, size: Size) -> Self {
self.panel_size = size;
self
}
pub fn with_escape_closes(mut self, close: bool) -> Self {
self.escape_closes = close;
self
}
pub fn with_on_close(mut self, on_close: impl Fn() + 'static) -> Self {
self.on_close = Some(Box::new(on_close));
self
}
pub fn with_default_action(mut self, action: impl Fn() + 'static) -> Self {
self.default_action = Some(Box::new(action));
self
}
pub fn with_cancel_action(mut self, action: impl Fn() + 'static) -> Self {
self.cancel_action = Some(Box::new(action));
self
}
pub fn visibility_cell(&self) -> Rc<Cell<bool>> {
Rc::clone(&self.visible)
}
fn close(&self) {
self.visible.set(false);
if let Some(on_close) = &self.on_close {
on_close();
}
crate::animation::request_draw();
}
fn activate_content(&mut self, path: &[usize]) -> bool {
let Some((&first, rest)) = path.split_first() else {
return false;
};
let Some(child) = self.children.get_mut(first) else {
return false;
};
activate_action_at(child.as_mut(), rest).is_consumed()
}
fn handle_enter(&mut self) -> bool {
if let Some(path) = default_action_path(self, false) {
if self.activate_content(&path) {
return true;
}
}
if let Some(action) = &self.default_action {
action();
crate::animation::request_draw();
return true;
}
false
}
fn handle_escape(&mut self) -> bool {
if let Some(path) = cancel_action_path(self, false) {
if self.activate_content(&path) {
return true;
}
}
if let Some(action) = &self.cancel_action {
action();
crate::animation::request_draw();
return true;
}
if self.escape_closes {
self.close();
return true;
}
false
}
}
impl Widget for ModalSheet {
fn type_name(&self) -> &'static str {
"ModalSheet"
}
fn bounds(&self) -> Rect {
self.bounds
}
fn set_bounds(&mut self, b: Rect) {
self.bounds = b;
}
fn children(&self) -> &[Box<dyn Widget>] {
&self.children
}
fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
&mut self.children
}
fn widget_base(&self) -> Option<&WidgetBase> {
Some(&self.base)
}
fn widget_base_mut(&mut self) -> Option<&mut WidgetBase> {
Some(&mut self.base)
}
fn is_visible(&self) -> bool {
self.visible.get()
}
fn has_active_modal(&self) -> bool {
self.visible.get()
}
fn layout(&mut self, available: Size) -> Size {
if !self.visible.get() {
self.bounds = Rect::new(0.0, 0.0, 0.0, 0.0);
return Size::new(0.0, 0.0);
}
let w = self
.panel_size
.width
.min(available.width - 2.0 * EDGE_MARGIN)
.max(0.0);
let h = self
.panel_size
.height
.min(available.height - 2.0 * EDGE_MARGIN)
.max(0.0);
let x = ((available.width - w) / 2.0).round();
let y = ((available.height - h) / 2.0).round();
self.panel = Rect::new(x, y, w, h);
if let Some(child) = self.children.first_mut() {
child.layout(Size::new(w, h));
child.set_bounds(self.panel);
}
self.bounds = Rect::new(0.0, 0.0, available.width, available.height);
available
}
fn paint(&mut self, ctx: &mut dyn DrawCtx) {
if !self.visible.get() {
return;
}
let v = current_visuals();
ctx.set_fill_color(Color::rgba(0.0, 0.0, 0.0, 0.20));
ctx.begin_path();
ctx.rect(0.0, 0.0, self.bounds.width, self.bounds.height);
ctx.fill();
let style = ChromeStyle {
corner_radius: CORNER_R,
..ChromeStyle::from_visuals(&v)
};
ctx.save();
ctx.translate(self.panel.x, self.panel.y);
paint_chrome_shadow(ctx, self.panel.width, self.panel.height, &style);
ctx.set_fill_color(v.window_fill);
ctx.begin_path();
ctx.rounded_rect(0.0, 0.0, self.panel.width, self.panel.height, CORNER_R);
ctx.fill();
ctx.set_stroke_color(v.window_stroke);
ctx.set_line_width(1.0);
ctx.begin_path();
ctx.rounded_rect(
0.5,
0.5,
self.panel.width - 1.0,
self.panel.height - 1.0,
CORNER_R,
);
ctx.stroke();
ctx.restore();
}
fn on_event(&mut self, event: &Event) -> EventResult {
if !self.visible.get() {
return EventResult::Ignored;
}
match event {
Event::MouseDown { .. } | Event::MouseUp { .. } | Event::MouseWheel { .. } => {
EventResult::Consumed
}
Event::KeyDown {
key: Key::Escape, ..
} if self.handle_escape() => EventResult::Consumed,
Event::KeyDown {
key: Key::Enter, ..
} if self.handle_enter() => EventResult::Consumed,
Event::KeyDown { .. } | Event::KeyUp { .. } if !self.key_passthrough => {
EventResult::Consumed
}
_ => EventResult::Ignored,
}
}
}