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}