agg_gui/widgets/
modal_sheet.rs1use std::cell::Cell;
21use std::rc::Rc;
22
23use crate::color::Color;
24use crate::draw_ctx::DrawCtx;
25use crate::event::{Event, EventResult, Key};
26use crate::geometry::{Rect, Size};
27use crate::layout_props::WidgetBase;
28use crate::theme::current_visuals;
29use crate::widget::Widget;
30use crate::widgets::window::chrome::{paint_chrome_shadow, ChromeStyle};
31
32const EDGE_MARGIN: f64 = 24.0;
35const CORNER_R: f64 = 10.0;
36
37pub struct ModalSheet {
39 bounds: Rect,
40 children: Vec<Box<dyn Widget>>, base: WidgetBase,
42 visible: Rc<Cell<bool>>,
43 panel_size: Size,
45 panel: Rect,
47 escape_closes: bool,
49 key_passthrough: bool,
53 on_close: Option<Box<dyn Fn()>>,
57}
58
59impl ModalSheet {
60 pub fn new(visible: Rc<Cell<bool>>, content: Box<dyn Widget>) -> Self {
61 Self {
62 bounds: Rect::default(),
63 children: vec![content],
64 base: WidgetBase::new(),
65 visible,
66 panel_size: Size::new(480.0, 360.0),
67 panel: Rect::default(),
68 escape_closes: true,
69 key_passthrough: false,
70 on_close: None,
71 }
72 }
73
74 pub fn with_key_passthrough(mut self, passthrough: bool) -> Self {
77 self.key_passthrough = passthrough;
78 self
79 }
80
81 pub fn with_panel_size(mut self, size: Size) -> Self {
83 self.panel_size = size;
84 self
85 }
86
87 pub fn with_escape_closes(mut self, close: bool) -> Self {
89 self.escape_closes = close;
90 self
91 }
92
93 pub fn with_on_close(mut self, on_close: impl Fn() + 'static) -> Self {
95 self.on_close = Some(Box::new(on_close));
96 self
97 }
98
99 pub fn visibility_cell(&self) -> Rc<Cell<bool>> {
100 Rc::clone(&self.visible)
101 }
102
103 fn close(&self) {
104 self.visible.set(false);
105 if let Some(on_close) = &self.on_close {
106 on_close();
107 }
108 crate::animation::request_draw();
109 }
110}
111
112impl Widget for ModalSheet {
113 fn type_name(&self) -> &'static str {
114 "ModalSheet"
115 }
116
117 fn bounds(&self) -> Rect {
118 self.bounds
119 }
120
121 fn set_bounds(&mut self, b: Rect) {
122 self.bounds = b;
123 }
124
125 fn children(&self) -> &[Box<dyn Widget>] {
126 &self.children
127 }
128
129 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
130 &mut self.children
131 }
132
133 fn widget_base(&self) -> Option<&WidgetBase> {
134 Some(&self.base)
135 }
136
137 fn widget_base_mut(&mut self) -> Option<&mut WidgetBase> {
138 Some(&mut self.base)
139 }
140
141 fn is_visible(&self) -> bool {
142 self.visible.get()
143 }
144
145 fn has_active_modal(&self) -> bool {
146 self.visible.get()
147 }
148
149 fn layout(&mut self, available: Size) -> Size {
150 if !self.visible.get() {
151 self.bounds = Rect::new(0.0, 0.0, 0.0, 0.0);
152 return Size::new(0.0, 0.0);
153 }
154 let w = self
155 .panel_size
156 .width
157 .min(available.width - 2.0 * EDGE_MARGIN)
158 .max(0.0);
159 let h = self
160 .panel_size
161 .height
162 .min(available.height - 2.0 * EDGE_MARGIN)
163 .max(0.0);
164 let x = ((available.width - w) / 2.0).round();
166 let y = ((available.height - h) / 2.0).round();
167 self.panel = Rect::new(x, y, w, h);
168 if let Some(child) = self.children.first_mut() {
169 child.layout(Size::new(w, h));
170 child.set_bounds(self.panel);
171 }
172 self.bounds = Rect::new(0.0, 0.0, available.width, available.height);
173 available
174 }
175
176 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
177 if !self.visible.get() {
178 return;
179 }
180 let v = current_visuals();
181
182 ctx.set_fill_color(Color::rgba(0.0, 0.0, 0.0, 0.20));
184 ctx.begin_path();
185 ctx.rect(0.0, 0.0, self.bounds.width, self.bounds.height);
186 ctx.fill();
187
188 let style = ChromeStyle {
190 corner_radius: CORNER_R,
191 ..ChromeStyle::from_visuals(&v)
192 };
193 ctx.save();
194 ctx.translate(self.panel.x, self.panel.y);
195 paint_chrome_shadow(ctx, self.panel.width, self.panel.height, &style);
196 ctx.set_fill_color(v.window_fill);
197 ctx.begin_path();
198 ctx.rounded_rect(0.0, 0.0, self.panel.width, self.panel.height, CORNER_R);
199 ctx.fill();
200 ctx.set_stroke_color(v.window_stroke);
201 ctx.set_line_width(1.0);
202 ctx.begin_path();
203 ctx.rounded_rect(
204 0.5,
205 0.5,
206 self.panel.width - 1.0,
207 self.panel.height - 1.0,
208 CORNER_R,
209 );
210 ctx.stroke();
211 ctx.restore();
212 }
213
214 fn on_event(&mut self, event: &Event) -> EventResult {
215 if !self.visible.get() {
216 return EventResult::Ignored;
217 }
218 match event {
219 Event::MouseDown { .. } | Event::MouseUp { .. } | Event::MouseWheel { .. } => {
223 EventResult::Consumed
224 }
225 Event::KeyDown {
226 key: Key::Escape, ..
227 } if self.escape_closes => {
228 self.close();
229 EventResult::Consumed
230 }
231 Event::KeyDown { .. } | Event::KeyUp { .. } if !self.key_passthrough => {
235 EventResult::Consumed
236 }
237 _ => EventResult::Ignored,
238 }
239 }
240}