Skip to main content

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/// A closure that renders content within a column using [`ViewCtx`].
11type ColumnFn<'a, Msg> = Box<dyn FnOnce(&mut ViewCtx<'_, Msg>) + 'a>;
12
13/// Context passed to view functions, enabling message emission from any depth
14///
15/// # Example
16/// ```ignore
17/// fn view(model: &Model, ctx: &mut ViewCtx<Msg>) {
18///     if ctx.ui.button("Click me").clicked() {
19///         ctx.emit(Msg::ButtonClicked);
20///     }
21///
22///     // Or use the helper
23///     ctx.button("Another", Msg::AnotherClicked);
24/// }
25/// ```
26pub struct ViewCtx<'a, Msg> {
27    /// The egui UI handle
28    pub ui: &'a mut Ui,
29    /// Collected messages to be processed after view
30    emitter: &'a mut Vec<Msg>,
31}
32
33impl<'a, Msg> ViewCtx<'a, Msg> {
34    /// Create a new ViewCtx
35    pub(crate) fn new(ui: &'a mut Ui, emitter: &'a mut Vec<Msg>) -> Self {
36        Self { ui, emitter }
37    }
38
39    /// Emit a message to be processed in the next update cycle
40    #[inline]
41    pub fn emit(&mut self, msg: Msg) {
42        self.emitter.push(msg);
43    }
44
45    /// Emit multiple messages
46    pub fn emit_all(&mut self, msgs: impl IntoIterator<Item = Msg>) {
47        self.emitter.extend(msgs);
48    }
49
50    /// Emit a Result, converting Ok/Err to appropriate messages
51    ///
52    /// # Example
53    /// ```ignore
54    /// ctx.emit_result(
55    ///     parse_input(&text),
56    ///     |value| Msg::Parsed(value),
57    ///     |err| Msg::Error(err.to_string()),
58    /// );
59    /// ```
60    pub fn emit_result<T, E>(
61        &mut self,
62        result: Result<T, E>,
63        on_ok: impl FnOnce(T) -> Msg,
64        on_err: impl FnOnce(E) -> Msg,
65    ) {
66        match result {
67            Ok(value) => self.emit(on_ok(value)),
68            Err(err) => self.emit(on_err(err)),
69        }
70    }
71
72    /// Emit only if Result is Err (for error-only handling)
73    ///
74    /// # Example
75    /// ```ignore
76    /// ctx.emit_if_err(
77    ///     validate(&input),
78    ///     |err| Msg::ValidationError(err),
79    /// );
80    /// ```
81    pub fn emit_if_err<T, E>(&mut self, result: Result<T, E>, on_err: impl FnOnce(E) -> Msg) {
82        if let Err(err) = result {
83            self.emit(on_err(err));
84        }
85    }
86
87    /// Create a child context for nested UI regions
88    ///
89    /// Messages from the child are mapped to parent messages via `map_msg`
90    pub fn child<'b, ChildMsg, F>(
91        &'b mut self,
92        child_emitter: &'b mut Vec<ChildMsg>,
93        ui: &'b mut Ui,
94    ) -> ViewCtx<'b, ChildMsg> {
95        ViewCtx {
96            ui,
97            emitter: child_emitter,
98        }
99    }
100
101    /// Reborrow with same emitter but different UI (for nested layouts)
102    pub fn with_ui<'b>(&'b mut self, ui: &'b mut Ui) -> ViewCtx<'b, Msg>
103    where
104        'a: 'b,
105    {
106        ViewCtx {
107            ui,
108            emitter: self.emitter,
109        }
110    }
111}
112
113// Convenience methods for common patterns
114impl<'a, Msg> ViewCtx<'a, Msg> {
115    /// Button that emits a message when clicked
116    pub fn button(&mut self, text: impl Into<String>, msg: Msg) -> bool {
117        let clicked = self.ui.button(text.into()).clicked();
118        if clicked {
119            self.emit(msg);
120        }
121        clicked
122    }
123
124    /// Horizontal layout
125    pub fn horizontal<R>(&mut self, f: impl FnOnce(&mut ViewCtx<'_, Msg>) -> R) -> R {
126        let mut child_msgs = Vec::new();
127        let result = self
128            .ui
129            .horizontal(|ui| {
130                let mut child_ctx = ViewCtx::new(ui, &mut child_msgs);
131                f(&mut child_ctx)
132            })
133            .inner;
134        self.emitter.extend(child_msgs);
135        result
136    }
137
138    /// Vertical layout
139    pub fn vertical<R>(&mut self, f: impl FnOnce(&mut ViewCtx<'_, Msg>) -> R) -> R {
140        let mut child_msgs = Vec::new();
141        let result = self
142            .ui
143            .vertical(|ui| {
144                let mut child_ctx = ViewCtx::new(ui, &mut child_msgs);
145                f(&mut child_ctx)
146            })
147            .inner;
148        self.emitter.extend(child_msgs);
149        result
150    }
151
152    /// Group (framed region)
153    pub fn group<R>(&mut self, f: impl FnOnce(&mut ViewCtx<'_, Msg>) -> R) -> R {
154        let mut child_msgs = Vec::new();
155        let result = self
156            .ui
157            .group(|ui| {
158                let mut child_ctx = ViewCtx::new(ui, &mut child_msgs);
159                f(&mut child_ctx)
160            })
161            .inner;
162        self.emitter.extend(child_msgs);
163        result
164    }
165
166    /// Collapsing header
167    pub fn collapsing<R>(
168        &mut self,
169        heading: impl Into<String>,
170        f: impl FnOnce(&mut ViewCtx<'_, Msg>) -> R,
171    ) -> Option<R> {
172        let mut child_msgs = Vec::new();
173        let result = self
174            .ui
175            .collapsing(heading.into(), |ui| {
176                let mut child_ctx = ViewCtx::new(ui, &mut child_msgs);
177                f(&mut child_ctx)
178            })
179            .body_returned;
180        self.emitter.extend(child_msgs);
181        result
182    }
183
184    /// Scroll area (vertical, default settings)
185    pub fn scroll_area<R>(&mut self, f: impl FnOnce(&mut ViewCtx<'_, Msg>) -> R) -> R {
186        self.scroll_area_with(|area| area, f)
187    }
188
189    /// Scroll area with custom id (avoids ID clashes)
190    pub fn scroll_area_id<R>(
191        &mut self,
192        id: impl std::hash::Hash,
193        f: impl FnOnce(&mut ViewCtx<'_, Msg>) -> R,
194    ) -> R {
195        self.scroll_area_with(|area| area.id_salt(id), f)
196    }
197
198    /// Scroll area with full customization via builder
199    ///
200    /// # Example
201    /// ```ignore
202    /// ctx.scroll_area_with(
203    ///     |area| area.max_height(300.0).auto_shrink([false, false]),
204    ///     |ctx| {
205    ///         for i in 0..100 {
206    ///             ctx.ui.label(format!("Item {}", i));
207    ///         }
208    ///     },
209    /// );
210    /// ```
211    pub fn scroll_area_with<R>(
212        &mut self,
213        builder: impl FnOnce(egui::ScrollArea) -> egui::ScrollArea,
214        f: impl FnOnce(&mut ViewCtx<'_, Msg>) -> R,
215    ) -> R {
216        let mut child_msgs = Vec::new();
217        let area = builder(egui::ScrollArea::vertical());
218        let result = area
219            .show(self.ui, |ui| {
220                let mut child_ctx = ViewCtx::new(ui, &mut child_msgs);
221                f(&mut child_ctx)
222            })
223            .inner;
224        self.emitter.extend(child_msgs);
225        result
226    }
227
228    /// Two-panel layout with left sidebar
229    ///
230    /// Uses egui::SidePanel internally for clean layout.
231    ///
232    /// # Example
233    /// ```ignore
234    /// ctx.sidebar_layout(
235    ///     "my_sidebar",
236    ///     200.0,
237    ///     |ctx| {
238    ///         // Sidebar content
239    ///         ctx.ui.label("Navigation");
240    ///     },
241    ///     |ctx| {
242    ///         // Main content
243    ///         ctx.ui.label("Content");
244    ///     },
245    /// );
246    /// ```
247    pub fn sidebar_layout(
248        &mut self,
249        id: impl Into<egui::Id>,
250        width: f32,
251        sidebar: impl FnOnce(&mut ViewCtx<'_, Msg>),
252        main: impl FnOnce(&mut ViewCtx<'_, Msg>),
253    ) {
254        let mut sidebar_msgs = Vec::new();
255        let mut main_msgs = Vec::new();
256        let egui_ctx = self.ui.ctx().clone();
257
258        // Left sidebar
259        egui::SidePanel::left(id)
260            .exact_width(width)
261            .show(&egui_ctx, |ui| {
262                let mut ctx = ViewCtx::new(ui, &mut sidebar_msgs);
263                sidebar(&mut ctx);
264            });
265
266        // Main panel
267        egui::CentralPanel::default().show(&egui_ctx, |ui| {
268            let mut ctx = ViewCtx::new(ui, &mut main_msgs);
269            main(&mut ctx);
270        });
271
272        self.emitter.extend(sidebar_msgs);
273        self.emitter.extend(main_msgs);
274    }
275
276    /// Two-panel layout with right sidebar
277    pub fn sidebar_right_layout(
278        &mut self,
279        id: impl Into<egui::Id>,
280        width: f32,
281        main: impl FnOnce(&mut ViewCtx<'_, Msg>),
282        sidebar: impl FnOnce(&mut ViewCtx<'_, Msg>),
283    ) {
284        let mut sidebar_msgs = Vec::new();
285        let mut main_msgs = Vec::new();
286        let egui_ctx = self.ui.ctx().clone();
287
288        // Right sidebar
289        egui::SidePanel::right(id)
290            .exact_width(width)
291            .show(&egui_ctx, |ui| {
292                let mut ctx = ViewCtx::new(ui, &mut sidebar_msgs);
293                sidebar(&mut ctx);
294            });
295
296        // Main panel
297        egui::CentralPanel::default().show(&egui_ctx, |ui| {
298            let mut ctx = ViewCtx::new(ui, &mut main_msgs);
299            main(&mut ctx);
300        });
301
302        self.emitter.extend(main_msgs);
303        self.emitter.extend(sidebar_msgs);
304    }
305
306    /// Top + Main panel layout
307    pub fn top_panel_layout(
308        &mut self,
309        id: impl Into<egui::Id>,
310        top: impl FnOnce(&mut ViewCtx<'_, Msg>),
311        main: impl FnOnce(&mut ViewCtx<'_, Msg>),
312    ) {
313        let mut top_msgs = Vec::new();
314        let mut main_msgs = Vec::new();
315        let egui_ctx = self.ui.ctx().clone();
316
317        // Top panel
318        egui::TopBottomPanel::top(id).show(&egui_ctx, |ui| {
319            let mut ctx = ViewCtx::new(ui, &mut top_msgs);
320            top(&mut ctx);
321        });
322
323        // Main panel
324        egui::CentralPanel::default().show(&egui_ctx, |ui| {
325            let mut ctx = ViewCtx::new(ui, &mut main_msgs);
326            main(&mut ctx);
327        });
328
329        self.emitter.extend(top_msgs);
330        self.emitter.extend(main_msgs);
331    }
332
333    /// Two-column layout using allocate_ui_at_rect
334    ///
335    /// Divides the available space into two equal columns.
336    /// Each column gets its own ViewCtx with full emit() capability.
337    ///
338    /// # Example
339    /// ```ignore
340    /// ctx.two_columns(
341    ///     |ctx| {
342    ///         ctx.ui.label("Left column");
343    ///         ctx.button("Click", Msg::LeftClicked);
344    ///     },
345    ///     |ctx| {
346    ///         ctx.ui.label("Right column");
347    ///         ctx.button("Click", Msg::RightClicked);
348    ///     },
349    /// );
350    /// ```
351    pub fn two_columns(
352        &mut self,
353        left: impl FnOnce(&mut ViewCtx<'_, Msg>),
354        right: impl FnOnce(&mut ViewCtx<'_, Msg>),
355    ) {
356        self.columns_n::<2>([Box::new(left), Box::new(right)]);
357    }
358
359    /// Three-column layout
360    ///
361    /// Divides the available space into three equal columns.
362    pub fn three_columns(
363        &mut self,
364        col1: impl FnOnce(&mut ViewCtx<'_, Msg>),
365        col2: impl FnOnce(&mut ViewCtx<'_, Msg>),
366        col3: impl FnOnce(&mut ViewCtx<'_, Msg>),
367    ) {
368        self.columns_n::<3>([Box::new(col1), Box::new(col2), Box::new(col3)]);
369    }
370
371    /// Four-column layout
372    ///
373    /// Divides the available space into four equal columns.
374    pub fn four_columns(
375        &mut self,
376        col1: impl FnOnce(&mut ViewCtx<'_, Msg>),
377        col2: impl FnOnce(&mut ViewCtx<'_, Msg>),
378        col3: impl FnOnce(&mut ViewCtx<'_, Msg>),
379        col4: impl FnOnce(&mut ViewCtx<'_, Msg>),
380    ) {
381        self.columns_n::<4>([
382            Box::new(col1),
383            Box::new(col2),
384            Box::new(col3),
385            Box::new(col4),
386        ]);
387    }
388
389    /// Variable-length column layout
390    ///
391    /// Divides the available space into N equal columns.
392    /// Use this when you need more than 4 columns or dynamic column count.
393    ///
394    /// # Example
395    /// ```ignore
396    /// ctx.columns(vec![
397    ///     Box::new(|ctx| { ctx.ui.label("Col 1"); }),
398    ///     Box::new(|ctx| { ctx.ui.label("Col 2"); }),
399    ///     Box::new(|ctx| { ctx.ui.label("Col 3"); }),
400    ///     Box::new(|ctx| { ctx.ui.label("Col 4"); }),
401    ///     Box::new(|ctx| { ctx.ui.label("Col 5"); }),
402    /// ]);
403    /// ```
404    pub fn columns(&mut self, columns: Vec<ColumnFn<'_, Msg>>) {
405        let n = columns.len();
406        if n == 0 {
407            return;
408        }
409
410        let mut all_msgs: Vec<Vec<Msg>> = (0..n).map(|_| Vec::new()).collect();
411        let mut columns: Vec<_> = columns.into_iter().map(Some).collect();
412
413        self.ui.columns(n, |cols| {
414            for i in 0..n {
415                if let Some(col_fn) = columns[i].take() {
416                    let mut ctx = ViewCtx::new(&mut cols[i], &mut all_msgs[i]);
417                    col_fn(&mut ctx);
418                }
419            }
420        });
421
422        for msgs in all_msgs {
423            self.emitter.extend(msgs);
424        }
425    }
426
427    /// Internal helper for N-column layout
428    fn columns_n<const N: usize>(&mut self, columns: [ColumnFn<'_, Msg>; N]) {
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}