egui_cha/view_ctx.rs
1//! ViewCtx - The bridge between view and message emission
2
3use std::hash::Hash;
4
5use egui::Ui;
6
7use crate::bindings::{ActionBindings, InputBinding};
8use crate::drag_drop::{DragSourceResponse, DropZoneResponse};
9
10/// Context passed to view functions, enabling message emission from any depth
11///
12/// # Example
13/// ```ignore
14/// fn view(model: &Model, ctx: &mut ViewCtx<Msg>) {
15/// if ctx.ui.button("Click me").clicked() {
16/// ctx.emit(Msg::ButtonClicked);
17/// }
18///
19/// // Or use the helper
20/// ctx.button("Another", Msg::AnotherClicked);
21/// }
22/// ```
23pub struct ViewCtx<'a, Msg> {
24 /// The egui UI handle
25 pub ui: &'a mut Ui,
26 /// Collected messages to be processed after view
27 emitter: &'a mut Vec<Msg>,
28}
29
30impl<'a, Msg> ViewCtx<'a, Msg> {
31 /// Create a new ViewCtx
32 pub(crate) fn new(ui: &'a mut Ui, emitter: &'a mut Vec<Msg>) -> Self {
33 Self { ui, emitter }
34 }
35
36 /// Emit a message to be processed in the next update cycle
37 #[inline]
38 pub fn emit(&mut self, msg: Msg) {
39 self.emitter.push(msg);
40 }
41
42 /// Emit multiple messages
43 pub fn emit_all(&mut self, msgs: impl IntoIterator<Item = Msg>) {
44 self.emitter.extend(msgs);
45 }
46
47 /// Emit a Result, converting Ok/Err to appropriate messages
48 ///
49 /// # Example
50 /// ```ignore
51 /// ctx.emit_result(
52 /// parse_input(&text),
53 /// |value| Msg::Parsed(value),
54 /// |err| Msg::Error(err.to_string()),
55 /// );
56 /// ```
57 pub fn emit_result<T, E>(
58 &mut self,
59 result: Result<T, E>,
60 on_ok: impl FnOnce(T) -> Msg,
61 on_err: impl FnOnce(E) -> Msg,
62 ) {
63 match result {
64 Ok(value) => self.emit(on_ok(value)),
65 Err(err) => self.emit(on_err(err)),
66 }
67 }
68
69 /// Emit only if Result is Err (for error-only handling)
70 ///
71 /// # Example
72 /// ```ignore
73 /// ctx.emit_if_err(
74 /// validate(&input),
75 /// |err| Msg::ValidationError(err),
76 /// );
77 /// ```
78 pub fn emit_if_err<T, E>(&mut self, result: Result<T, E>, on_err: impl FnOnce(E) -> Msg) {
79 if let Err(err) = result {
80 self.emit(on_err(err));
81 }
82 }
83
84 /// Create a child context for nested UI regions
85 ///
86 /// Messages from the child are mapped to parent messages via `map_msg`
87 pub fn child<'b, ChildMsg, F>(
88 &'b mut self,
89 child_emitter: &'b mut Vec<ChildMsg>,
90 ui: &'b mut Ui,
91 ) -> ViewCtx<'b, ChildMsg> {
92 ViewCtx {
93 ui,
94 emitter: child_emitter,
95 }
96 }
97
98 /// Reborrow with same emitter but different UI (for nested layouts)
99 pub fn with_ui<'b>(&'b mut self, ui: &'b mut Ui) -> ViewCtx<'b, Msg>
100 where
101 'a: 'b,
102 {
103 ViewCtx {
104 ui,
105 emitter: self.emitter,
106 }
107 }
108}
109
110// Convenience methods for common patterns
111impl<'a, Msg> ViewCtx<'a, Msg> {
112 /// Button that emits a message when clicked
113 pub fn button(&mut self, text: impl Into<String>, msg: Msg) -> bool {
114 let clicked = self.ui.button(text.into()).clicked();
115 if clicked {
116 self.emit(msg);
117 }
118 clicked
119 }
120
121 /// Horizontal layout
122 pub fn horizontal<R>(&mut self, f: impl FnOnce(&mut ViewCtx<'_, Msg>) -> R) -> R {
123 let mut child_msgs = Vec::new();
124 let result = self
125 .ui
126 .horizontal(|ui| {
127 let mut child_ctx = ViewCtx::new(ui, &mut child_msgs);
128 f(&mut child_ctx)
129 })
130 .inner;
131 self.emitter.extend(child_msgs);
132 result
133 }
134
135 /// Vertical layout
136 pub fn vertical<R>(&mut self, f: impl FnOnce(&mut ViewCtx<'_, Msg>) -> R) -> R {
137 let mut child_msgs = Vec::new();
138 let result = self
139 .ui
140 .vertical(|ui| {
141 let mut child_ctx = ViewCtx::new(ui, &mut child_msgs);
142 f(&mut child_ctx)
143 })
144 .inner;
145 self.emitter.extend(child_msgs);
146 result
147 }
148
149 /// Group (framed region)
150 pub fn group<R>(&mut self, f: impl FnOnce(&mut ViewCtx<'_, Msg>) -> R) -> R {
151 let mut child_msgs = Vec::new();
152 let result = self
153 .ui
154 .group(|ui| {
155 let mut child_ctx = ViewCtx::new(ui, &mut child_msgs);
156 f(&mut child_ctx)
157 })
158 .inner;
159 self.emitter.extend(child_msgs);
160 result
161 }
162
163 /// Collapsing header
164 pub fn collapsing<R>(
165 &mut self,
166 heading: impl Into<String>,
167 f: impl FnOnce(&mut ViewCtx<'_, Msg>) -> R,
168 ) -> Option<R> {
169 let mut child_msgs = Vec::new();
170 let result = self
171 .ui
172 .collapsing(heading.into(), |ui| {
173 let mut child_ctx = ViewCtx::new(ui, &mut child_msgs);
174 f(&mut child_ctx)
175 })
176 .body_returned;
177 self.emitter.extend(child_msgs);
178 result
179 }
180
181 /// Scroll area (vertical, default settings)
182 pub fn scroll_area<R>(&mut self, f: impl FnOnce(&mut ViewCtx<'_, Msg>) -> R) -> R {
183 self.scroll_area_with(|area| area, f)
184 }
185
186 /// Scroll area with custom id (avoids ID clashes)
187 pub fn scroll_area_id<R>(
188 &mut self,
189 id: impl std::hash::Hash,
190 f: impl FnOnce(&mut ViewCtx<'_, Msg>) -> R,
191 ) -> R {
192 self.scroll_area_with(|area| area.id_salt(id), f)
193 }
194
195 /// Scroll area with full customization via builder
196 ///
197 /// # Example
198 /// ```ignore
199 /// ctx.scroll_area_with(
200 /// |area| area.max_height(300.0).auto_shrink([false, false]),
201 /// |ctx| {
202 /// for i in 0..100 {
203 /// ctx.ui.label(format!("Item {}", i));
204 /// }
205 /// },
206 /// );
207 /// ```
208 pub fn scroll_area_with<R>(
209 &mut self,
210 builder: impl FnOnce(egui::ScrollArea) -> egui::ScrollArea,
211 f: impl FnOnce(&mut ViewCtx<'_, Msg>) -> R,
212 ) -> R {
213 let mut child_msgs = Vec::new();
214 let area = builder(egui::ScrollArea::vertical());
215 let result = area
216 .show(self.ui, |ui| {
217 let mut child_ctx = ViewCtx::new(ui, &mut child_msgs);
218 f(&mut child_ctx)
219 })
220 .inner;
221 self.emitter.extend(child_msgs);
222 result
223 }
224
225 /// Two-panel layout with left sidebar
226 ///
227 /// Uses egui::SidePanel internally for clean layout.
228 ///
229 /// # Example
230 /// ```ignore
231 /// ctx.sidebar_layout(
232 /// "my_sidebar",
233 /// 200.0,
234 /// |ctx| {
235 /// // Sidebar content
236 /// ctx.ui.label("Navigation");
237 /// },
238 /// |ctx| {
239 /// // Main content
240 /// ctx.ui.label("Content");
241 /// },
242 /// );
243 /// ```
244 pub fn sidebar_layout(
245 &mut self,
246 id: impl Into<egui::Id>,
247 width: f32,
248 sidebar: impl FnOnce(&mut ViewCtx<'_, Msg>),
249 main: impl FnOnce(&mut ViewCtx<'_, Msg>),
250 ) {
251 let mut sidebar_msgs = Vec::new();
252 let mut main_msgs = Vec::new();
253 let egui_ctx = self.ui.ctx().clone();
254
255 // Left sidebar
256 egui::SidePanel::left(id)
257 .exact_width(width)
258 .show(&egui_ctx, |ui| {
259 let mut ctx = ViewCtx::new(ui, &mut sidebar_msgs);
260 sidebar(&mut ctx);
261 });
262
263 // Main panel
264 egui::CentralPanel::default().show(&egui_ctx, |ui| {
265 let mut ctx = ViewCtx::new(ui, &mut main_msgs);
266 main(&mut ctx);
267 });
268
269 self.emitter.extend(sidebar_msgs);
270 self.emitter.extend(main_msgs);
271 }
272
273 /// Two-panel layout with right sidebar
274 pub fn sidebar_right_layout(
275 &mut self,
276 id: impl Into<egui::Id>,
277 width: f32,
278 main: impl FnOnce(&mut ViewCtx<'_, Msg>),
279 sidebar: impl FnOnce(&mut ViewCtx<'_, Msg>),
280 ) {
281 let mut sidebar_msgs = Vec::new();
282 let mut main_msgs = Vec::new();
283 let egui_ctx = self.ui.ctx().clone();
284
285 // Right sidebar
286 egui::SidePanel::right(id)
287 .exact_width(width)
288 .show(&egui_ctx, |ui| {
289 let mut ctx = ViewCtx::new(ui, &mut sidebar_msgs);
290 sidebar(&mut ctx);
291 });
292
293 // Main panel
294 egui::CentralPanel::default().show(&egui_ctx, |ui| {
295 let mut ctx = ViewCtx::new(ui, &mut main_msgs);
296 main(&mut ctx);
297 });
298
299 self.emitter.extend(main_msgs);
300 self.emitter.extend(sidebar_msgs);
301 }
302
303 /// Top + Main panel layout
304 pub fn top_panel_layout(
305 &mut self,
306 id: impl Into<egui::Id>,
307 top: impl FnOnce(&mut ViewCtx<'_, Msg>),
308 main: impl FnOnce(&mut ViewCtx<'_, Msg>),
309 ) {
310 let mut top_msgs = Vec::new();
311 let mut main_msgs = Vec::new();
312 let egui_ctx = self.ui.ctx().clone();
313
314 // Top panel
315 egui::TopBottomPanel::top(id).show(&egui_ctx, |ui| {
316 let mut ctx = ViewCtx::new(ui, &mut top_msgs);
317 top(&mut ctx);
318 });
319
320 // Main panel
321 egui::CentralPanel::default().show(&egui_ctx, |ui| {
322 let mut ctx = ViewCtx::new(ui, &mut main_msgs);
323 main(&mut ctx);
324 });
325
326 self.emitter.extend(top_msgs);
327 self.emitter.extend(main_msgs);
328 }
329
330 /// Two-column layout using allocate_ui_at_rect
331 ///
332 /// Divides the available space into two equal columns.
333 /// Each column gets its own ViewCtx with full emit() capability.
334 ///
335 /// # Example
336 /// ```ignore
337 /// ctx.two_columns(
338 /// |ctx| {
339 /// ctx.ui.label("Left column");
340 /// ctx.button("Click", Msg::LeftClicked);
341 /// },
342 /// |ctx| {
343 /// ctx.ui.label("Right column");
344 /// ctx.button("Click", Msg::RightClicked);
345 /// },
346 /// );
347 /// ```
348 pub fn two_columns(
349 &mut self,
350 left: impl FnOnce(&mut ViewCtx<'_, Msg>),
351 right: impl FnOnce(&mut ViewCtx<'_, Msg>),
352 ) {
353 self.columns_n::<2>([Box::new(left), Box::new(right)]);
354 }
355
356 /// Three-column layout
357 ///
358 /// Divides the available space into three equal columns.
359 pub fn three_columns(
360 &mut self,
361 col1: impl FnOnce(&mut ViewCtx<'_, Msg>),
362 col2: impl FnOnce(&mut ViewCtx<'_, Msg>),
363 col3: impl FnOnce(&mut ViewCtx<'_, Msg>),
364 ) {
365 self.columns_n::<3>([Box::new(col1), Box::new(col2), Box::new(col3)]);
366 }
367
368 /// Four-column layout
369 ///
370 /// Divides the available space into four equal columns.
371 pub fn four_columns(
372 &mut self,
373 col1: impl FnOnce(&mut ViewCtx<'_, Msg>),
374 col2: impl FnOnce(&mut ViewCtx<'_, Msg>),
375 col3: impl FnOnce(&mut ViewCtx<'_, Msg>),
376 col4: impl FnOnce(&mut ViewCtx<'_, Msg>),
377 ) {
378 self.columns_n::<4>([
379 Box::new(col1),
380 Box::new(col2),
381 Box::new(col3),
382 Box::new(col4),
383 ]);
384 }
385
386 /// Variable-length column layout
387 ///
388 /// Divides the available space into N equal columns.
389 /// Use this when you need more than 4 columns or dynamic column count.
390 ///
391 /// # Example
392 /// ```ignore
393 /// ctx.columns(vec![
394 /// Box::new(|ctx| { ctx.ui.label("Col 1"); }),
395 /// Box::new(|ctx| { ctx.ui.label("Col 2"); }),
396 /// Box::new(|ctx| { ctx.ui.label("Col 3"); }),
397 /// Box::new(|ctx| { ctx.ui.label("Col 4"); }),
398 /// Box::new(|ctx| { ctx.ui.label("Col 5"); }),
399 /// ]);
400 /// ```
401 pub fn columns(&mut self, columns: Vec<Box<dyn FnOnce(&mut ViewCtx<'_, Msg>) + '_>>) {
402 let n = columns.len();
403 if n == 0 {
404 return;
405 }
406
407 let mut all_msgs: Vec<Vec<Msg>> = (0..n).map(|_| Vec::new()).collect();
408 let mut columns: Vec<_> = columns.into_iter().map(Some).collect();
409
410 self.ui.columns(n, |cols| {
411 for i in 0..n {
412 if let Some(col_fn) = columns[i].take() {
413 let mut ctx = ViewCtx::new(&mut cols[i], &mut all_msgs[i]);
414 col_fn(&mut ctx);
415 }
416 }
417 });
418
419 for msgs in all_msgs {
420 self.emitter.extend(msgs);
421 }
422 }
423
424 /// Internal helper for N-column layout
425 fn columns_n<const N: usize>(
426 &mut self,
427 columns: [Box<dyn FnOnce(&mut ViewCtx<'_, Msg>) + '_>; N],
428 ) {
429 let mut all_msgs: Vec<Vec<Msg>> = (0..N).map(|_| Vec::new()).collect();
430 let mut columns: Vec<_> = columns.into_iter().map(Some).collect();
431
432 self.ui.columns(N, |cols| {
433 for i in 0..N {
434 if let Some(col_fn) = columns[i].take() {
435 let mut ctx = ViewCtx::new(&mut cols[i], &mut all_msgs[i]);
436 col_fn(&mut ctx);
437 }
438 }
439 });
440
441 for msgs in all_msgs {
442 self.emitter.extend(msgs);
443 }
444 }
445
446 /// Conditionally show content
447 ///
448 /// # Example
449 /// ```ignore
450 /// ctx.show_if(model.is_logged_in, |ctx| {
451 /// ctx.ui.label("Welcome!");
452 /// ctx.button("Logout", Msg::Logout);
453 /// });
454 /// ```
455 pub fn show_if<R>(
456 &mut self,
457 condition: bool,
458 f: impl FnOnce(&mut ViewCtx<'_, Msg>) -> R,
459 ) -> Option<R> {
460 if condition {
461 Some(f(self))
462 } else {
463 None
464 }
465 }
466
467 /// Conditionally show content with else branch
468 ///
469 /// # Example
470 /// ```ignore
471 /// ctx.show_if_else(
472 /// model.is_logged_in,
473 /// |ctx| { ctx.ui.label("Welcome!"); },
474 /// |ctx| { ctx.button("Login", Msg::Login); },
475 /// );
476 /// ```
477 pub fn show_if_else<R>(
478 &mut self,
479 condition: bool,
480 if_true: impl FnOnce(&mut ViewCtx<'_, Msg>) -> R,
481 if_false: impl FnOnce(&mut ViewCtx<'_, Msg>) -> R,
482 ) -> R {
483 if condition {
484 if_true(self)
485 } else {
486 if_false(self)
487 }
488 }
489
490 /// Render content with disabled state
491 ///
492 /// # Example
493 /// ```ignore
494 /// ctx.enabled_if(model.can_submit, |ctx| {
495 /// ctx.button("Submit", Msg::Submit);
496 /// });
497 /// ```
498 pub fn enabled_if<R>(
499 &mut self,
500 enabled: bool,
501 f: impl FnOnce(&mut ViewCtx<'_, Msg>) -> R,
502 ) -> R {
503 self.ui
504 .add_enabled_ui(enabled, |ui| {
505 let mut child_msgs = Vec::new();
506 let mut ctx = ViewCtx::new(ui, &mut child_msgs);
507 let result = f(&mut ctx);
508 self.emitter.extend(child_msgs);
509 result
510 })
511 .inner
512 }
513
514 /// Render content with visible state (hidden but still takes space)
515 ///
516 /// # Example
517 /// ```ignore
518 /// ctx.visible_if(model.show_hint, |ctx| {
519 /// ctx.ui.label("This is a hint");
520 /// });
521 /// ```
522 pub fn visible_if<R>(
523 &mut self,
524 visible: bool,
525 f: impl FnOnce(&mut ViewCtx<'_, Msg>) -> R,
526 ) -> R {
527 self.ui
528 .scope(|ui| {
529 if !visible {
530 ui.set_invisible();
531 }
532 let mut child_msgs = Vec::new();
533 let mut ctx = ViewCtx::new(ui, &mut child_msgs);
534 let result = f(&mut ctx);
535 self.emitter.extend(child_msgs);
536 result
537 })
538 .inner
539 }
540}
541
542// Keyboard shortcuts support
543impl<'a, Msg> ViewCtx<'a, Msg> {
544 /// Check if a keyboard shortcut was pressed and emit a message
545 ///
546 /// Uses `consume_shortcut` internally, so the shortcut won't trigger
547 /// other handlers after being consumed.
548 ///
549 /// # Example
550 /// ```ignore
551 /// use egui::{Key, KeyboardShortcut, Modifiers};
552 ///
553 /// // Define shortcuts
554 /// const SAVE: KeyboardShortcut = KeyboardShortcut::new(Modifiers::COMMAND, Key::S);
555 /// const UNDO: KeyboardShortcut = KeyboardShortcut::new(Modifiers::COMMAND, Key::Z);
556 ///
557 /// // In view function
558 /// ctx.on_shortcut(SAVE, Msg::Save);
559 /// ctx.on_shortcut(UNDO, Msg::Undo);
560 /// ```
561 pub fn on_shortcut(&mut self, shortcut: egui::KeyboardShortcut, msg: Msg) -> bool {
562 let triggered = self.ui.ctx().input_mut(|i| i.consume_shortcut(&shortcut));
563 if triggered {
564 self.emit(msg);
565 }
566 triggered
567 }
568
569 /// Check multiple shortcuts at once
570 ///
571 /// More efficient than calling `on_shortcut` multiple times.
572 ///
573 /// # Example
574 /// ```ignore
575 /// ctx.on_shortcuts(&[
576 /// (SAVE, Msg::Save),
577 /// (UNDO, Msg::Undo),
578 /// (REDO, Msg::Redo),
579 /// ]);
580 /// ```
581 pub fn on_shortcuts(&mut self, shortcuts: &[(egui::KeyboardShortcut, Msg)])
582 where
583 Msg: Clone,
584 {
585 for (shortcut, msg) in shortcuts {
586 self.on_shortcut(*shortcut, msg.clone());
587 }
588 }
589
590 /// Check if a specific key was pressed (without modifiers)
591 ///
592 /// # Example
593 /// ```ignore
594 /// ctx.on_key(Key::Escape, Msg::Cancel);
595 /// ctx.on_key(Key::Enter, Msg::Confirm);
596 /// ```
597 pub fn on_key(&mut self, key: egui::Key, msg: Msg) -> bool {
598 let pressed = self.ui.ctx().input(|i| i.key_pressed(key));
599 if pressed {
600 self.emit(msg);
601 }
602 pressed
603 }
604
605 /// Check if an input binding was triggered and emit a message.
606 ///
607 /// Works with any type implementing `InputBinding`, including
608 /// `KeyboardShortcut`, `DynamicShortcut`, and `ShortcutGroup`.
609 ///
610 /// # Example
611 /// ```ignore
612 /// use egui_cha::bindings::{DynamicShortcut, InputBinding};
613 ///
614 /// let custom = DynamicShortcut::new(Modifiers::CTRL, Key::K);
615 /// ctx.on_binding(&custom, Msg::Custom);
616 ///
617 /// // Also works with static shortcuts
618 /// ctx.on_binding(&shortcuts::SAVE, Msg::Save);
619 /// ```
620 pub fn on_binding(&mut self, binding: &impl InputBinding, msg: Msg) -> bool {
621 let ctx = self.ui.ctx().clone();
622 if binding.consume(&ctx) {
623 self.emit(msg);
624 true
625 } else {
626 false
627 }
628 }
629
630 /// Check if an action from ActionBindings was triggered.
631 ///
632 /// This is the preferred way to handle keyboard shortcuts when
633 /// using the dynamic binding system.
634 ///
635 /// # Example
636 /// ```ignore
637 /// use egui_cha::bindings::ActionBindings;
638 ///
639 /// #[derive(Clone, PartialEq, Eq, Hash)]
640 /// enum Action { Save, Undo, Redo }
641 ///
642 /// let bindings = ActionBindings::new()
643 /// .with_default(Action::Save, shortcuts::SAVE)
644 /// .with_default(Action::Undo, shortcuts::UNDO);
645 ///
646 /// // In view function
647 /// ctx.on_action(&bindings, &Action::Save, Msg::Save);
648 /// ctx.on_action(&bindings, &Action::Undo, Msg::Undo);
649 /// ```
650 pub fn on_action<A>(&mut self, bindings: &ActionBindings<A>, action: &A, msg: Msg) -> bool
651 where
652 A: Eq + Hash + Clone,
653 {
654 if let Some(shortcut) = bindings.get(action) {
655 self.on_binding(shortcut, msg)
656 } else {
657 false
658 }
659 }
660
661 /// Check all actions in ActionBindings and return triggered ones.
662 ///
663 /// Useful when you want to handle multiple actions in a single call.
664 ///
665 /// # Example
666 /// ```ignore
667 /// for action in ctx.triggered_actions(&bindings) {
668 /// match action {
669 /// Action::Save => ctx.emit(Msg::Save),
670 /// Action::Undo => ctx.emit(Msg::Undo),
671 /// _ => {}
672 /// }
673 /// }
674 /// ```
675 pub fn triggered_actions<A>(&mut self, bindings: &ActionBindings<A>) -> Option<A>
676 where
677 A: Eq + Hash + Clone,
678 {
679 let ctx = self.ui.ctx().clone();
680 bindings.check_triggered(&ctx).cloned()
681 }
682}
683
684// Drag & Drop support
685impl<'a, Msg> ViewCtx<'a, Msg> {
686 /// Create a draggable source
687 ///
688 /// # Example
689 /// ```ignore
690 /// ctx.drag_source("item_1", item.clone(), |ctx| {
691 /// ctx.ui.label(&item.name);
692 /// }).on_drag_start(ctx, Msg::DragStart { id: item.id });
693 /// ```
694 pub fn drag_source<P, R>(
695 &mut self,
696 id: impl Into<egui::Id>,
697 payload: P,
698 content: impl FnOnce(&mut ViewCtx<'_, Msg>) -> R,
699 ) -> DragSourceResponse<R>
700 where
701 P: Clone + Send + Sync + 'static,
702 {
703 let id = id.into();
704 let mut child_msgs = Vec::new();
705 let mut inner_result = None;
706 let mut drag_started = false;
707
708 let response = self
709 .ui
710 .dnd_drag_source(id, payload, |ui| {
711 let mut ctx = ViewCtx::new(ui, &mut child_msgs);
712 inner_result = Some(content(&mut ctx));
713 })
714 .response;
715
716 // Check if drag started this frame
717 if response.drag_started() {
718 drag_started = true;
719 }
720
721 self.emitter.extend(child_msgs);
722
723 DragSourceResponse {
724 inner: inner_result.expect("content closure should have been called"),
725 response,
726 drag_started,
727 }
728 }
729
730 /// Create a drop zone that accepts payloads of type P
731 ///
732 /// # Example
733 /// ```ignore
734 /// ctx.drop_zone::<Item, _>(|ctx| {
735 /// ctx.ui.label("Drop items here");
736 /// }).on_drop(ctx, |item| Msg::ItemDropped(item));
737 /// ```
738 pub fn drop_zone<P, R>(
739 &mut self,
740 content: impl FnOnce(&mut ViewCtx<'_, Msg>) -> R,
741 ) -> DropZoneResponse<P, R>
742 where
743 P: Clone + Send + Sync + 'static,
744 {
745 let mut child_msgs = Vec::new();
746
747 let (response, dropped_payload) =
748 self.ui.dnd_drop_zone::<P, _>(egui::Frame::default(), |ui| {
749 let mut ctx = ViewCtx::new(ui, &mut child_msgs);
750 content(&mut ctx)
751 });
752
753 // Check if being dragged over with compatible payload
754 let is_being_dragged_over = egui::DragAndDrop::has_payload_of_type::<P>(self.ui.ctx());
755
756 self.emitter.extend(child_msgs);
757
758 DropZoneResponse {
759 inner: response.inner,
760 response: response.response,
761 payload: dropped_payload,
762 is_being_dragged_over,
763 }
764 }
765}