egui/
response.rs

1use std::{any::Any, sync::Arc};
2
3use crate::{
4    emath::{Align, Pos2, Rect, Vec2},
5    menu, pass_state, AreaState, Context, CursorIcon, Id, LayerId, Order, PointerButton, Sense, Ui,
6    WidgetRect, WidgetText,
7};
8// ----------------------------------------------------------------------------
9
10/// The result of adding a widget to a [`Ui`].
11///
12/// A [`Response`] lets you know whether a widget is being hovered, clicked or dragged.
13/// It also lets you easily show a tooltip on hover.
14///
15/// Whenever something gets added to a [`Ui`], a [`Response`] object is returned.
16/// [`ui.add`] returns a [`Response`], as does [`ui.button`], and all similar shortcuts.
17///
18/// ⚠️ The `Response` contains a clone of [`Context`], and many methods lock the `Context`.
19/// It can therefore be a deadlock to use `Context` from within a context-locking closures,
20/// such as [`Context::input`].
21#[derive(Clone, Debug)]
22pub struct Response {
23    // CONTEXT:
24    /// Used for optionally showing a tooltip and checking for more interactions.
25    pub ctx: Context,
26
27    // IN:
28    /// Which layer the widget is part of.
29    pub layer_id: LayerId,
30
31    /// The [`Id`] of the widget/area this response pertains.
32    pub id: Id,
33
34    /// The area of the screen we are talking about.
35    pub rect: Rect,
36
37    /// The rectangle sensing interaction.
38    ///
39    /// This is sometimes smaller than [`Self::rect`] because of clipping
40    /// (e.g. when inside a scroll area).
41    pub interact_rect: Rect,
42
43    /// The senses (click and/or drag) that the widget was interested in (if any).
44    ///
45    /// Note: if [`Self::enabled`] is `false`, then
46    /// the widget _effectively_ doesn't sense anything,
47    /// but can still have the same `Sense`.
48    /// This is because the sense informs the styling of the widget,
49    /// but we don't want to change the style when a widget is disabled
50    /// (that is handled by the `Painter` directly).
51    pub sense: Sense,
52
53    // OUT:
54    /// Where the pointer (mouse/touch) were when this widget was clicked or dragged.
55    /// `None` if the widget is not being interacted with.
56    #[doc(hidden)]
57    pub interact_pointer_pos: Option<Pos2>,
58
59    /// The intrinsic / desired size of the widget.
60    ///
61    /// For a button, this will be the size of the label + the frames padding,
62    /// even if the button is laid out in a justified layout and the actual size will be larger.
63    ///
64    /// If this is `None`, use [`Self::rect`] instead.
65    ///
66    /// At the time of writing, this is only used by external crates
67    /// for improved layouting.
68    /// See for instance [`egui_flex`](https://github.com/lucasmerlin/hello_egui/tree/main/crates/egui_flex).
69    pub intrinsic_size: Option<Vec2>,
70
71    #[doc(hidden)]
72    pub flags: Flags,
73}
74
75/// A bit set for various boolean properties of `Response`.
76#[doc(hidden)]
77#[derive(Copy, Clone, Debug)]
78pub struct Flags(u16);
79
80bitflags::bitflags! {
81    impl Flags: u16 {
82        /// Was the widget enabled?
83        /// If `false`, there was no interaction attempted (not even hover).
84        const ENABLED = 1<<0;
85
86        /// The pointer is above this widget with no other blocking it.
87        const CONTAINS_POINTER = 1<<1;
88
89        /// The pointer is hovering above this widget or the widget was clicked/tapped this frame.
90        const HOVERED = 1<<2;
91
92        /// The widget is highlighted via a call to [`Response::highlight`] or
93        /// [`Context::highlight_widget`].
94        const HIGHLIGHTED = 1<<3;
95
96        /// This widget was clicked this frame.
97        ///
98        /// Which pointer and how many times we don't know,
99        /// and ask [`crate::InputState`] about at runtime.
100        ///
101        /// This is only set to true if the widget was clicked
102        /// by an actual mouse.
103        const CLICKED = 1<<4;
104
105        /// This widget should act as if clicked due
106        /// to something else than a click.
107        ///
108        /// This is set to true if the widget has keyboard focus and
109        /// the user hit the Space or Enter key.
110        const FAKE_PRIMARY_CLICKED = 1<<5;
111
112        /// This widget was long-pressed on a touch screen to simulate a secondary click.
113        const LONG_TOUCHED = 1<<6;
114
115        /// The widget started being dragged this frame.
116        const DRAG_STARTED = 1<<7;
117
118        /// The widget is being dragged.
119        const DRAGGED = 1<<8;
120
121        /// The widget was being dragged, but now it has been released.
122        const DRAG_STOPPED = 1<<9;
123
124        /// Is the pointer button currently down on this widget?
125        /// This is true if the pointer is pressing down or dragging a widget
126        const IS_POINTER_BUTTON_DOWN_ON = 1<<10;
127
128        /// Was the underlying data changed?
129        ///
130        /// e.g. the slider was dragged, text was entered in a [`TextEdit`](crate::TextEdit) etc.
131        /// Always `false` for something like a [`Button`](crate::Button).
132        ///
133        /// Note that this can be `true` even if the user did not interact with the widget,
134        /// for instance if an existing slider value was clamped to the given range.
135        const CHANGED = 1<<11;
136    }
137}
138
139impl Response {
140    /// Returns true if this widget was clicked this frame by the primary button.
141    ///
142    /// A click is registered when the mouse or touch is released within
143    /// a certain amount of time and distance from when and where it was pressed.
144    ///
145    /// This will also return true if the widget was clicked via accessibility integration,
146    /// or if the widget had keyboard focus and the use pressed Space/Enter.
147    ///
148    /// Note that the widget must be sensing clicks with [`Sense::click`].
149    /// [`crate::Button`] senses clicks; [`crate::Label`] does not (unless you call [`crate::Label::sense`]).
150    ///
151    /// You can use [`Self::interact`] to sense more things *after* adding a widget.
152    #[inline(always)]
153    pub fn clicked(&self) -> bool {
154        self.flags.contains(Flags::FAKE_PRIMARY_CLICKED) || self.clicked_by(PointerButton::Primary)
155    }
156
157    /// Returns true if this widget was clicked this frame by the given mouse button.
158    ///
159    /// This will NOT return true if the widget was "clicked" via
160    /// some accessibility integration, or if the widget had keyboard focus and the
161    /// user pressed Space/Enter. For that, use [`Self::clicked`] instead.
162    ///
163    /// This will likewise ignore the press-and-hold action on touch screens.
164    /// Use [`Self::secondary_clicked`] instead to also detect that.
165    #[inline]
166    pub fn clicked_by(&self, button: PointerButton) -> bool {
167        self.flags.contains(Flags::CLICKED) && self.ctx.input(|i| i.pointer.button_clicked(button))
168    }
169
170    /// Returns true if this widget was clicked this frame by the secondary mouse button (e.g. the right mouse button).
171    ///
172    /// This also returns true if the widget was pressed-and-held on a touch screen.
173    #[inline]
174    pub fn secondary_clicked(&self) -> bool {
175        self.flags.contains(Flags::LONG_TOUCHED) || self.clicked_by(PointerButton::Secondary)
176    }
177
178    /// Was this long-pressed on a touch screen?
179    ///
180    /// Usually you want to check [`Self::secondary_clicked`] instead.
181    #[inline]
182    pub fn long_touched(&self) -> bool {
183        self.flags.contains(Flags::LONG_TOUCHED)
184    }
185
186    /// Returns true if this widget was clicked this frame by the middle mouse button.
187    #[inline]
188    pub fn middle_clicked(&self) -> bool {
189        self.clicked_by(PointerButton::Middle)
190    }
191
192    /// Returns true if this widget was double-clicked this frame by the primary button.
193    #[inline]
194    pub fn double_clicked(&self) -> bool {
195        self.double_clicked_by(PointerButton::Primary)
196    }
197
198    /// Returns true if this widget was triple-clicked this frame by the primary button.
199    #[inline]
200    pub fn triple_clicked(&self) -> bool {
201        self.triple_clicked_by(PointerButton::Primary)
202    }
203
204    /// Returns true if this widget was double-clicked this frame by the given button.
205    #[inline]
206    pub fn double_clicked_by(&self, button: PointerButton) -> bool {
207        self.flags.contains(Flags::CLICKED)
208            && self.ctx.input(|i| i.pointer.button_double_clicked(button))
209    }
210
211    /// Returns true if this widget was triple-clicked this frame by the given button.
212    #[inline]
213    pub fn triple_clicked_by(&self, button: PointerButton) -> bool {
214        self.flags.contains(Flags::CLICKED)
215            && self.ctx.input(|i| i.pointer.button_triple_clicked(button))
216    }
217
218    /// `true` if there was a click *outside* the rect of this widget.
219    ///
220    /// Clicks on widgets contained in this one counts as clicks inside this widget,
221    /// so that clicking a button in an area will not be considered as clicking "elsewhere" from the area.
222    pub fn clicked_elsewhere(&self) -> bool {
223        // We do not use self.clicked(), because we want to catch all clicks within our frame,
224        // even if we aren't clickable (or even enabled).
225        // This is important for windows and such that should close then the user clicks elsewhere.
226        self.ctx.input(|i| {
227            let pointer = &i.pointer;
228
229            if pointer.any_click() {
230                if self.contains_pointer() || self.hovered() {
231                    false
232                } else if let Some(pos) = pointer.interact_pos() {
233                    !self.interact_rect.contains(pos)
234                } else {
235                    false // clicked without a pointer, weird
236                }
237            } else {
238                false
239            }
240        })
241    }
242
243    /// Was the widget enabled?
244    /// If false, there was no interaction attempted
245    /// and the widget should be drawn in a gray disabled look.
246    #[inline(always)]
247    pub fn enabled(&self) -> bool {
248        self.flags.contains(Flags::ENABLED)
249    }
250
251    /// The pointer is hovering above this widget or the widget was clicked/tapped this frame.
252    ///
253    /// In contrast to [`Self::contains_pointer`], this will be `false` whenever some other widget is being dragged.
254    /// `hovered` is always `false` for disabled widgets.
255    #[inline(always)]
256    pub fn hovered(&self) -> bool {
257        self.flags.contains(Flags::HOVERED)
258    }
259
260    /// Returns true if the pointer is contained by the response rect, and no other widget is covering it.
261    ///
262    /// In contrast to [`Self::hovered`], this can be `true` even if some other widget is being dragged.
263    /// This means it is useful for styling things like drag-and-drop targets.
264    /// `contains_pointer` can also be `true` for disabled widgets.
265    ///
266    /// This is slightly different from [`Ui::rect_contains_pointer`] and [`Context::rect_contains_pointer`], in that
267    /// [`Self::contains_pointer`] also checks that no other widget is covering this response rectangle.
268    #[inline(always)]
269    pub fn contains_pointer(&self) -> bool {
270        self.flags.contains(Flags::CONTAINS_POINTER)
271    }
272
273    /// The widget is highlighted via a call to [`Self::highlight`] or [`Context::highlight_widget`].
274    #[doc(hidden)]
275    #[inline(always)]
276    pub fn highlighted(&self) -> bool {
277        self.flags.contains(Flags::HIGHLIGHTED)
278    }
279
280    /// This widget has the keyboard focus (i.e. is receiving key presses).
281    ///
282    /// This function only returns true if the UI as a whole (e.g. window)
283    /// also has the keyboard focus. That makes this function suitable
284    /// for style choices, e.g. a thicker border around focused widgets.
285    pub fn has_focus(&self) -> bool {
286        self.ctx.input(|i| i.focused) && self.ctx.memory(|mem| mem.has_focus(self.id))
287    }
288
289    /// True if this widget has keyboard focus this frame, but didn't last frame.
290    pub fn gained_focus(&self) -> bool {
291        self.ctx.memory(|mem| mem.gained_focus(self.id))
292    }
293
294    /// The widget had keyboard focus and lost it,
295    /// either because the user pressed tab or clicked somewhere else,
296    /// or (in case of a [`crate::TextEdit`]) because the user pressed enter.
297    ///
298    /// ```
299    /// # egui::__run_test_ui(|ui| {
300    /// # let mut my_text = String::new();
301    /// # fn do_request(_: &str) {}
302    /// let response = ui.text_edit_singleline(&mut my_text);
303    /// if response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) {
304    ///     do_request(&my_text);
305    /// }
306    /// # });
307    /// ```
308    pub fn lost_focus(&self) -> bool {
309        self.ctx.memory(|mem| mem.lost_focus(self.id))
310    }
311
312    /// Request that this widget get keyboard focus.
313    pub fn request_focus(&self) {
314        self.ctx.memory_mut(|mem| mem.request_focus(self.id));
315    }
316
317    /// Surrender keyboard focus for this widget.
318    pub fn surrender_focus(&self) {
319        self.ctx.memory_mut(|mem| mem.surrender_focus(self.id));
320    }
321
322    /// Did a drag on this widget begin this frame?
323    ///
324    /// This is only true if the widget sense drags.
325    /// If the widget also senses clicks, this will only become true if the pointer has moved a bit.
326    ///
327    /// This will only be true for a single frame.
328    #[inline]
329    pub fn drag_started(&self) -> bool {
330        self.flags.contains(Flags::DRAG_STARTED)
331    }
332
333    /// Did a drag on this widget by the button begin this frame?
334    ///
335    /// This is only true if the widget sense drags.
336    /// If the widget also senses clicks, this will only become true if the pointer has moved a bit.
337    ///
338    /// This will only be true for a single frame.
339    #[inline]
340    pub fn drag_started_by(&self, button: PointerButton) -> bool {
341        self.drag_started() && self.ctx.input(|i| i.pointer.button_down(button))
342    }
343
344    /// The widget is being dragged.
345    ///
346    /// To find out which button(s), use [`Self::dragged_by`].
347    ///
348    /// If the widget is only sensitive to drags, this is `true` as soon as the pointer presses down on it.
349    /// If the widget also senses clicks, this won't be true until the pointer has moved a bit,
350    /// or the user has pressed down for long enough.
351    /// See [`crate::input_state::PointerState::is_decidedly_dragging`] for details.
352    ///
353    /// If you want to avoid the delay, use [`Self::is_pointer_button_down_on`] instead.
354    ///
355    /// If the widget is NOT sensitive to drags, this will always be `false`.
356    /// [`crate::DragValue`] senses drags; [`crate::Label`] does not (unless you call [`crate::Label::sense`]).
357    /// You can use [`Self::interact`] to sense more things *after* adding a widget.
358    #[inline(always)]
359    pub fn dragged(&self) -> bool {
360        self.flags.contains(Flags::DRAGGED)
361    }
362
363    /// See [`Self::dragged`].
364    #[inline]
365    pub fn dragged_by(&self, button: PointerButton) -> bool {
366        self.dragged() && self.ctx.input(|i| i.pointer.button_down(button))
367    }
368
369    /// The widget was being dragged, but now it has been released.
370    #[inline]
371    pub fn drag_stopped(&self) -> bool {
372        self.flags.contains(Flags::DRAG_STOPPED)
373    }
374
375    /// The widget was being dragged by the button, but now it has been released.
376    pub fn drag_stopped_by(&self, button: PointerButton) -> bool {
377        self.drag_stopped() && self.ctx.input(|i| i.pointer.button_released(button))
378    }
379
380    /// The widget was being dragged, but now it has been released.
381    #[inline]
382    #[deprecated = "Renamed 'drag_stopped'"]
383    pub fn drag_released(&self) -> bool {
384        self.drag_stopped()
385    }
386
387    /// The widget was being dragged by the button, but now it has been released.
388    #[deprecated = "Renamed 'drag_stopped_by'"]
389    pub fn drag_released_by(&self, button: PointerButton) -> bool {
390        self.drag_stopped_by(button)
391    }
392
393    /// If dragged, how many points were we dragged and in what direction?
394    #[inline]
395    pub fn drag_delta(&self) -> Vec2 {
396        if self.dragged() {
397            let mut delta = self.ctx.input(|i| i.pointer.delta());
398            if let Some(from_global) = self.ctx.layer_transform_from_global(self.layer_id) {
399                delta *= from_global.scaling;
400            }
401            delta
402        } else {
403            Vec2::ZERO
404        }
405    }
406
407    /// If dragged, how far did the mouse move?
408    /// This will use raw mouse movement if provided by the integration, otherwise will fall back to [`Response::drag_delta`]
409    /// Raw mouse movement is unaccelerated and unclamped by screen boundaries, and does not relate to any position on the screen.
410    /// This may be useful in certain situations such as draggable values and 3D cameras, where screen position does not matter.
411    #[inline]
412    pub fn drag_motion(&self) -> Vec2 {
413        if self.dragged() {
414            self.ctx
415                .input(|i| i.pointer.motion().unwrap_or(i.pointer.delta()))
416        } else {
417            Vec2::ZERO
418        }
419    }
420
421    /// If the user started dragging this widget this frame, store the payload for drag-and-drop.
422    #[doc(alias = "drag and drop")]
423    pub fn dnd_set_drag_payload<Payload: Any + Send + Sync>(&self, payload: Payload) {
424        if self.drag_started() {
425            crate::DragAndDrop::set_payload(&self.ctx, payload);
426        }
427
428        if self.hovered() && !self.sense.senses_click() {
429            // Things that can be drag-dropped should use the Grab cursor icon,
430            // but if the thing is _also_ clickable, that can be annoying.
431            self.ctx.set_cursor_icon(CursorIcon::Grab);
432        }
433    }
434
435    /// Drag-and-Drop: Return what is being held over this widget, if any.
436    ///
437    /// Only returns something if [`Self::contains_pointer`] is true,
438    /// and the user is drag-dropping something of this type.
439    #[doc(alias = "drag and drop")]
440    pub fn dnd_hover_payload<Payload: Any + Send + Sync>(&self) -> Option<Arc<Payload>> {
441        // NOTE: we use `response.contains_pointer` here instead of `hovered`, because
442        // `hovered` is always false when another widget is being dragged.
443        if self.contains_pointer() {
444            crate::DragAndDrop::payload::<Payload>(&self.ctx)
445        } else {
446            None
447        }
448    }
449
450    /// Drag-and-Drop: Return what is being dropped onto this widget, if any.
451    ///
452    /// Only returns something if [`Self::contains_pointer`] is true,
453    /// the user is drag-dropping something of this type,
454    /// and they released it this frame
455    #[doc(alias = "drag and drop")]
456    pub fn dnd_release_payload<Payload: Any + Send + Sync>(&self) -> Option<Arc<Payload>> {
457        // NOTE: we use `response.contains_pointer` here instead of `hovered`, because
458        // `hovered` is always false when another widget is being dragged.
459        if self.contains_pointer() && self.ctx.input(|i| i.pointer.any_released()) {
460            crate::DragAndDrop::take_payload::<Payload>(&self.ctx)
461        } else {
462            None
463        }
464    }
465
466    /// Where the pointer (mouse/touch) were when this widget was clicked or dragged.
467    ///
468    /// `None` if the widget is not being interacted with.
469    #[inline]
470    pub fn interact_pointer_pos(&self) -> Option<Pos2> {
471        self.interact_pointer_pos
472    }
473
474    /// If it is a good idea to show a tooltip, where is pointer?
475    ///
476    /// None if the pointer is outside the response area.
477    #[inline]
478    pub fn hover_pos(&self) -> Option<Pos2> {
479        if self.hovered() {
480            let mut pos = self.ctx.input(|i| i.pointer.hover_pos())?;
481            if let Some(from_global) = self.ctx.layer_transform_from_global(self.layer_id) {
482                pos = from_global * pos;
483            }
484            Some(pos)
485        } else {
486            None
487        }
488    }
489
490    /// Is the pointer button currently down on this widget?
491    ///
492    /// This is true if the pointer is pressing down or dragging a widget,
493    /// even when dragging outside the widget.
494    ///
495    /// This could also be thought of as "is this widget being interacted with?".
496    #[inline(always)]
497    pub fn is_pointer_button_down_on(&self) -> bool {
498        self.flags.contains(Flags::IS_POINTER_BUTTON_DOWN_ON)
499    }
500
501    /// Was the underlying data changed?
502    ///
503    /// e.g. the slider was dragged, text was entered in a [`TextEdit`](crate::TextEdit) etc.
504    /// Always `false` for something like a [`Button`](crate::Button).
505    ///
506    /// Can sometimes be `true` even though the data didn't changed
507    /// (e.g. if the user entered a character and erased it the same frame).
508    ///
509    /// This is not set if the *view* of the data was changed.
510    /// For instance, moving the cursor in a [`TextEdit`](crate::TextEdit) does not set this to `true`.
511    ///
512    /// Note that this can be `true` even if the user did not interact with the widget,
513    /// for instance if an existing slider value was clamped to the given range.
514    #[inline(always)]
515    pub fn changed(&self) -> bool {
516        self.flags.contains(Flags::CHANGED)
517    }
518
519    /// Report the data shown by this widget changed.
520    ///
521    /// This must be called by widgets that represent some mutable data,
522    /// e.g. checkboxes, sliders etc.
523    ///
524    /// This should be called when the *content* changes, but not when the view does.
525    /// So we call this when the text of a [`crate::TextEdit`], but not when the cursor changes.
526    #[inline(always)]
527    pub fn mark_changed(&mut self) {
528        self.flags.set(Flags::CHANGED, true);
529    }
530
531    /// Show this UI if the widget was hovered (i.e. a tooltip).
532    ///
533    /// The text will not be visible if the widget is not enabled.
534    /// For that, use [`Self::on_disabled_hover_ui`] instead.
535    ///
536    /// If you call this multiple times the tooltips will stack underneath the previous ones.
537    ///
538    /// The widget can contain interactive widgets, such as buttons and links.
539    /// If so, it will stay open as the user moves their pointer over it.
540    /// By default, the text of a tooltip is NOT selectable (i.e. interactive),
541    /// but you can change this by setting [`style::Interaction::selectable_labels` from within the tooltip:
542    ///
543    /// ```
544    /// # egui::__run_test_ui(|ui| {
545    /// ui.label("Hover me").on_hover_ui(|ui| {
546    ///     ui.style_mut().interaction.selectable_labels = true;
547    ///     ui.label("This text can be selected");
548    /// });
549    /// # });
550    /// ```
551    #[doc(alias = "tooltip")]
552    pub fn on_hover_ui(self, add_contents: impl FnOnce(&mut Ui)) -> Self {
553        if self.flags.contains(Flags::ENABLED) && self.should_show_hover_ui() {
554            self.show_tooltip_ui(add_contents);
555        }
556        self
557    }
558
559    /// Show this UI when hovering if the widget is disabled.
560    pub fn on_disabled_hover_ui(self, add_contents: impl FnOnce(&mut Ui)) -> Self {
561        if !self.enabled() && self.should_show_hover_ui() {
562            crate::containers::show_tooltip_for(
563                &self.ctx,
564                self.layer_id,
565                self.id,
566                &self.rect,
567                add_contents,
568            );
569        }
570        self
571    }
572
573    /// Like `on_hover_ui`, but show the ui next to cursor.
574    pub fn on_hover_ui_at_pointer(self, add_contents: impl FnOnce(&mut Ui)) -> Self {
575        if self.enabled() && self.should_show_hover_ui() {
576            crate::containers::show_tooltip_at_pointer(
577                &self.ctx,
578                self.layer_id,
579                self.id,
580                add_contents,
581            );
582        }
583        self
584    }
585
586    /// Always show this tooltip, even if disabled and the user isn't hovering it.
587    ///
588    /// This can be used to give attention to a widget during a tutorial.
589    pub fn show_tooltip_ui(&self, add_contents: impl FnOnce(&mut Ui)) {
590        crate::containers::show_tooltip_for(
591            &self.ctx,
592            self.layer_id,
593            self.id,
594            &self.rect,
595            add_contents,
596        );
597    }
598
599    /// Always show this tooltip, even if disabled and the user isn't hovering it.
600    ///
601    /// This can be used to give attention to a widget during a tutorial.
602    pub fn show_tooltip_text(&self, text: impl Into<WidgetText>) {
603        self.show_tooltip_ui(|ui| {
604            ui.label(text);
605        });
606    }
607
608    /// Was the tooltip open last frame?
609    pub fn is_tooltip_open(&self) -> bool {
610        crate::popup::was_tooltip_open_last_frame(&self.ctx, self.id)
611    }
612
613    fn should_show_hover_ui(&self) -> bool {
614        if self.ctx.memory(|mem| mem.everything_is_visible()) {
615            return true;
616        }
617
618        let any_open_popups = self.ctx.prev_pass_state(|fs| {
619            fs.layers
620                .get(&self.layer_id)
621                .is_some_and(|layer| !layer.open_popups.is_empty())
622        });
623        if any_open_popups {
624            // Hide tooltips if the user opens a popup (menu, combo-box, etc) in the same layer.
625            return false;
626        }
627
628        let style = self.ctx.style();
629
630        let tooltip_delay = style.interaction.tooltip_delay;
631        let tooltip_grace_time = style.interaction.tooltip_grace_time;
632
633        let (
634            time_since_last_scroll,
635            time_since_last_click,
636            time_since_last_pointer_movement,
637            pointer_pos,
638            pointer_dir,
639        ) = self.ctx.input(|i| {
640            (
641                i.time_since_last_scroll(),
642                i.pointer.time_since_last_click(),
643                i.pointer.time_since_last_movement(),
644                i.pointer.hover_pos(),
645                i.pointer.direction(),
646            )
647        });
648
649        if time_since_last_scroll < tooltip_delay {
650            // See https://github.com/emilk/egui/issues/4781
651            // Note that this means we cannot have `ScrollArea`s in a tooltip.
652            self.ctx
653                .request_repaint_after_secs(tooltip_delay - time_since_last_scroll);
654            return false;
655        }
656
657        let is_our_tooltip_open = self.is_tooltip_open();
658
659        if is_our_tooltip_open {
660            // Check if we should automatically stay open:
661
662            let tooltip_id = crate::next_tooltip_id(&self.ctx, self.id);
663            let tooltip_layer_id = LayerId::new(Order::Tooltip, tooltip_id);
664
665            let tooltip_has_interactive_widget = self.ctx.viewport(|vp| {
666                vp.prev_pass
667                    .widgets
668                    .get_layer(tooltip_layer_id)
669                    .any(|w| w.enabled && w.sense.interactive())
670            });
671
672            if tooltip_has_interactive_widget {
673                // We keep the tooltip open if hovered,
674                // or if the pointer is on its way to it,
675                // so that the user can interact with the tooltip
676                // (i.e. click links that are in it).
677                if let Some(area) = AreaState::load(&self.ctx, tooltip_id) {
678                    let rect = area.rect();
679
680                    if let Some(pos) = pointer_pos {
681                        if rect.contains(pos) {
682                            return true; // hovering interactive tooltip
683                        }
684                        if pointer_dir != Vec2::ZERO
685                            && rect.intersects_ray(pos, pointer_dir.normalized())
686                        {
687                            return true; // on the way to interactive tooltip
688                        }
689                    }
690                }
691            }
692        }
693
694        let clicked_more_recently_than_moved =
695            time_since_last_click < time_since_last_pointer_movement + 0.1;
696        if clicked_more_recently_than_moved {
697            // It is common to click a widget and then rest the mouse there.
698            // It would be annoying to then see a tooltip for it immediately.
699            // Similarly, clicking should hide the existing tooltip.
700            // Only hovering should lead to a tooltip, not clicking.
701            // The offset is only to allow small movement just right after the click.
702            return false;
703        }
704
705        if is_our_tooltip_open {
706            // Check if we should automatically stay open:
707
708            if pointer_pos.is_some_and(|pointer_pos| self.rect.contains(pointer_pos)) {
709                // Handle the case of a big tooltip that covers the widget:
710                return true;
711            }
712        }
713
714        let is_other_tooltip_open = self.ctx.prev_pass_state(|fs| {
715            if let Some(already_open_tooltip) = fs
716                .layers
717                .get(&self.layer_id)
718                .and_then(|layer| layer.widget_with_tooltip)
719            {
720                already_open_tooltip != self.id
721            } else {
722                false
723            }
724        });
725        if is_other_tooltip_open {
726            // We only allow one tooltip per layer. First one wins. It is up to that tooltip to close itself.
727            return false;
728        }
729
730        // Fast early-outs:
731        if self.enabled() {
732            if !self.hovered() || !self.ctx.input(|i| i.pointer.has_pointer()) {
733                return false;
734            }
735        } else if !self.ctx.rect_contains_pointer(self.layer_id, self.rect) {
736            return false;
737        }
738
739        // There is a tooltip_delay before showing the first tooltip,
740        // but once one tooltip is show, moving the mouse cursor to
741        // another widget should show the tooltip for that widget right away.
742
743        // Let the user quickly move over some dead space to hover the next thing
744        let tooltip_was_recently_shown =
745            crate::popup::seconds_since_last_tooltip(&self.ctx) < tooltip_grace_time;
746
747        if !tooltip_was_recently_shown && !is_our_tooltip_open {
748            if style.interaction.show_tooltips_only_when_still {
749                // We only show the tooltip when the mouse pointer is still.
750                if !self
751                    .ctx
752                    .input(|i| i.pointer.is_still() && i.smooth_scroll_delta == Vec2::ZERO)
753                {
754                    // wait for mouse to stop
755                    self.ctx.request_repaint();
756                    return false;
757                }
758            }
759
760            let time_since_last_interaction = time_since_last_scroll
761                .min(time_since_last_pointer_movement)
762                .min(time_since_last_click);
763            let time_til_tooltip = tooltip_delay - time_since_last_interaction;
764
765            if 0.0 < time_til_tooltip {
766                // Wait until the mouse has been still for a while
767                self.ctx.request_repaint_after_secs(time_til_tooltip);
768                return false;
769            }
770        }
771
772        // We don't want tooltips of things while we are dragging them,
773        // but we do want tooltips while holding down on an item on a touch screen.
774        if self
775            .ctx
776            .input(|i| i.pointer.any_down() && i.pointer.has_moved_too_much_for_a_click)
777        {
778            return false;
779        }
780
781        // All checks passed: show the tooltip!
782
783        true
784    }
785
786    /// Like `on_hover_text`, but show the text next to cursor.
787    #[doc(alias = "tooltip")]
788    pub fn on_hover_text_at_pointer(self, text: impl Into<WidgetText>) -> Self {
789        self.on_hover_ui_at_pointer(|ui| {
790            // Prevent `Area` auto-sizing from shrinking tooltips with dynamic content.
791            // See https://github.com/emilk/egui/issues/5167
792            ui.set_max_width(ui.spacing().tooltip_width);
793
794            ui.add(crate::widgets::Label::new(text));
795        })
796    }
797
798    /// Show this text if the widget was hovered (i.e. a tooltip).
799    ///
800    /// The text will not be visible if the widget is not enabled.
801    /// For that, use [`Self::on_disabled_hover_text`] instead.
802    ///
803    /// If you call this multiple times the tooltips will stack underneath the previous ones.
804    #[doc(alias = "tooltip")]
805    pub fn on_hover_text(self, text: impl Into<WidgetText>) -> Self {
806        self.on_hover_ui(|ui| {
807            // Prevent `Area` auto-sizing from shrinking tooltips with dynamic content.
808            // See https://github.com/emilk/egui/issues/5167
809            ui.set_max_width(ui.spacing().tooltip_width);
810
811            ui.add(crate::widgets::Label::new(text));
812        })
813    }
814
815    /// Highlight this widget, to make it look like it is hovered, even if it isn't.
816    ///
817    /// The highlight takes one frame to take effect if you call this after the widget has been fully rendered.
818    ///
819    /// See also [`Context::highlight_widget`].
820    #[inline]
821    pub fn highlight(mut self) -> Self {
822        self.ctx.highlight_widget(self.id);
823        self.flags.set(Flags::HIGHLIGHTED, true);
824        self
825    }
826
827    /// Show this text when hovering if the widget is disabled.
828    pub fn on_disabled_hover_text(self, text: impl Into<WidgetText>) -> Self {
829        self.on_disabled_hover_ui(|ui| {
830            // Prevent `Area` auto-sizing from shrinking tooltips with dynamic content.
831            // See https://github.com/emilk/egui/issues/5167
832            ui.set_max_width(ui.spacing().tooltip_width);
833
834            ui.add(crate::widgets::Label::new(text));
835        })
836    }
837
838    /// When hovered, use this icon for the mouse cursor.
839    #[inline]
840    pub fn on_hover_cursor(self, cursor: CursorIcon) -> Self {
841        if self.hovered() {
842            self.ctx.set_cursor_icon(cursor);
843        }
844        self
845    }
846
847    /// When hovered or dragged, use this icon for the mouse cursor.
848    #[inline]
849    pub fn on_hover_and_drag_cursor(self, cursor: CursorIcon) -> Self {
850        if self.hovered() || self.dragged() {
851            self.ctx.set_cursor_icon(cursor);
852        }
853        self
854    }
855
856    /// Sense more interactions (e.g. sense clicks on a [`Response`] returned from a label).
857    ///
858    /// The interaction will occur on the same plane as the original widget,
859    /// i.e. if the response was from a widget behind button, the interaction will also be behind that button.
860    /// egui gives priority to the _last_ added widget (the one on top gets clicked first).
861    ///
862    /// Note that this call will not add any hover-effects to the widget, so when possible
863    /// it is better to give the widget a [`Sense`] instead, e.g. using [`crate::Label::sense`].
864    ///
865    /// Using this method on a `Response` that is the result of calling `union` on multiple `Response`s
866    /// is undefined behavior.
867    ///
868    /// ```
869    /// # egui::__run_test_ui(|ui| {
870    /// let horiz_response = ui.horizontal(|ui| {
871    ///     ui.label("hello");
872    /// }).response;
873    /// assert!(!horiz_response.clicked()); // ui's don't sense clicks by default
874    /// let horiz_response = horiz_response.interact(egui::Sense::click());
875    /// if horiz_response.clicked() {
876    ///     // The background behind the label was clicked
877    /// }
878    /// # });
879    /// ```
880    #[must_use]
881    pub fn interact(&self, sense: Sense) -> Self {
882        if (self.sense | sense) == self.sense {
883            // Early-out: we already sense everything we need to sense.
884            return self.clone();
885        }
886
887        self.ctx.create_widget(
888            WidgetRect {
889                layer_id: self.layer_id,
890                id: self.id,
891                rect: self.rect,
892                interact_rect: self.interact_rect,
893                sense: self.sense | sense,
894                enabled: self.enabled(),
895            },
896            true,
897        )
898    }
899
900    /// Adjust the scroll position until this UI becomes visible.
901    ///
902    /// If `align` is [`Align::TOP`] it means "put the top of the rect at the top of the scroll area", etc.
903    /// If `align` is `None`, it'll scroll enough to bring the UI into view.
904    ///
905    /// See also: [`Ui::scroll_to_cursor`], [`Ui::scroll_to_rect`]. [`Ui::scroll_with_delta`].
906    ///
907    /// ```
908    /// # egui::__run_test_ui(|ui| {
909    /// egui::ScrollArea::vertical().show(ui, |ui| {
910    ///     for i in 0..1000 {
911    ///         let response = ui.button("Scroll to me");
912    ///         if response.clicked() {
913    ///             response.scroll_to_me(Some(egui::Align::Center));
914    ///         }
915    ///     }
916    /// });
917    /// # });
918    /// ```
919    pub fn scroll_to_me(&self, align: Option<Align>) {
920        self.scroll_to_me_animation(align, self.ctx.style().scroll_animation);
921    }
922
923    /// Like [`Self::scroll_to_me`], but allows you to specify the [`crate::style::ScrollAnimation`].
924    pub fn scroll_to_me_animation(
925        &self,
926        align: Option<Align>,
927        animation: crate::style::ScrollAnimation,
928    ) {
929        self.ctx.pass_state_mut(|state| {
930            state.scroll_target[0] = Some(pass_state::ScrollTarget::new(
931                self.rect.x_range(),
932                align,
933                animation,
934            ));
935            state.scroll_target[1] = Some(pass_state::ScrollTarget::new(
936                self.rect.y_range(),
937                align,
938                animation,
939            ));
940        });
941    }
942
943    /// For accessibility.
944    ///
945    /// Call after interacting and potential calls to [`Self::mark_changed`].
946    pub fn widget_info(&self, make_info: impl Fn() -> crate::WidgetInfo) {
947        use crate::output::OutputEvent;
948
949        let event = if self.clicked() {
950            Some(OutputEvent::Clicked(make_info()))
951        } else if self.double_clicked() {
952            Some(OutputEvent::DoubleClicked(make_info()))
953        } else if self.triple_clicked() {
954            Some(OutputEvent::TripleClicked(make_info()))
955        } else if self.gained_focus() {
956            Some(OutputEvent::FocusGained(make_info()))
957        } else if self.changed() {
958            Some(OutputEvent::ValueChanged(make_info()))
959        } else {
960            None
961        };
962
963        if let Some(event) = event {
964            self.output_event(event);
965        } else {
966            #[cfg(feature = "accesskit")]
967            self.ctx.accesskit_node_builder(self.id, |builder| {
968                self.fill_accesskit_node_from_widget_info(builder, make_info());
969            });
970
971            self.ctx.register_widget_info(self.id, make_info);
972        }
973    }
974
975    pub fn output_event(&self, event: crate::output::OutputEvent) {
976        #[cfg(feature = "accesskit")]
977        self.ctx.accesskit_node_builder(self.id, |builder| {
978            self.fill_accesskit_node_from_widget_info(builder, event.widget_info().clone());
979        });
980
981        self.ctx
982            .register_widget_info(self.id, || event.widget_info().clone());
983
984        self.ctx.output_mut(|o| o.events.push(event));
985    }
986
987    #[cfg(feature = "accesskit")]
988    pub(crate) fn fill_accesskit_node_common(&self, builder: &mut accesskit::Node) {
989        if !self.enabled() {
990            builder.set_disabled();
991        }
992        builder.set_bounds(accesskit::Rect {
993            x0: self.rect.min.x.into(),
994            y0: self.rect.min.y.into(),
995            x1: self.rect.max.x.into(),
996            y1: self.rect.max.y.into(),
997        });
998        if self.sense.is_focusable() {
999            builder.add_action(accesskit::Action::Focus);
1000        }
1001        if self.sense.senses_click() {
1002            builder.add_action(accesskit::Action::Click);
1003        }
1004    }
1005
1006    #[cfg(feature = "accesskit")]
1007    fn fill_accesskit_node_from_widget_info(
1008        &self,
1009        builder: &mut accesskit::Node,
1010        info: crate::WidgetInfo,
1011    ) {
1012        use crate::WidgetType;
1013        use accesskit::{Role, Toggled};
1014
1015        self.fill_accesskit_node_common(builder);
1016        builder.set_role(match info.typ {
1017            WidgetType::Label => Role::Label,
1018            WidgetType::Link => Role::Link,
1019            WidgetType::TextEdit => Role::TextInput,
1020            WidgetType::Button | WidgetType::ImageButton | WidgetType::CollapsingHeader => {
1021                Role::Button
1022            }
1023            WidgetType::Image => Role::Image,
1024            WidgetType::Checkbox => Role::CheckBox,
1025            WidgetType::RadioButton => Role::RadioButton,
1026            WidgetType::RadioGroup => Role::RadioGroup,
1027            WidgetType::SelectableLabel => Role::Button,
1028            WidgetType::ComboBox => Role::ComboBox,
1029            WidgetType::Slider => Role::Slider,
1030            WidgetType::DragValue => Role::SpinButton,
1031            WidgetType::ColorButton => Role::ColorWell,
1032            WidgetType::ProgressIndicator => Role::ProgressIndicator,
1033            WidgetType::Window => Role::Window,
1034            WidgetType::Other => Role::Unknown,
1035        });
1036        if !info.enabled {
1037            builder.set_disabled();
1038        }
1039        if let Some(label) = info.label {
1040            if matches!(builder.role(), Role::Label) {
1041                builder.set_value(label);
1042            } else {
1043                builder.set_label(label);
1044            }
1045        }
1046        if let Some(value) = info.current_text_value {
1047            builder.set_value(value);
1048        }
1049        if let Some(value) = info.value {
1050            builder.set_numeric_value(value);
1051        }
1052        if let Some(selected) = info.selected {
1053            builder.set_toggled(if selected {
1054                Toggled::True
1055            } else {
1056                Toggled::False
1057            });
1058        } else if matches!(info.typ, WidgetType::Checkbox) {
1059            // Indeterminate state
1060            builder.set_toggled(Toggled::Mixed);
1061        }
1062    }
1063
1064    /// Associate a label with a control for accessibility.
1065    ///
1066    /// # Example
1067    ///
1068    /// ```
1069    /// # egui::__run_test_ui(|ui| {
1070    /// # let mut text = "Arthur".to_string();
1071    /// ui.horizontal(|ui| {
1072    ///     let label = ui.label("Your name: ");
1073    ///     ui.text_edit_singleline(&mut text).labelled_by(label.id);
1074    /// });
1075    /// # });
1076    /// ```
1077    pub fn labelled_by(self, id: Id) -> Self {
1078        #[cfg(feature = "accesskit")]
1079        self.ctx.accesskit_node_builder(self.id, |builder| {
1080            builder.push_labelled_by(id.accesskit_id());
1081        });
1082        #[cfg(not(feature = "accesskit"))]
1083        {
1084            let _ = id;
1085        }
1086
1087        self
1088    }
1089
1090    /// Response to secondary clicks (right-clicks) by showing the given menu.
1091    ///
1092    /// Make sure the widget senses clicks (e.g. [`crate::Button`] does, [`crate::Label`] does not).
1093    ///
1094    /// ```
1095    /// # use egui::{Label, Sense};
1096    /// # egui::__run_test_ui(|ui| {
1097    /// let response = ui.add(Label::new("Right-click me!").sense(Sense::click()));
1098    /// response.context_menu(|ui| {
1099    ///     if ui.button("Close the menu").clicked() {
1100    ///         ui.close_menu();
1101    ///     }
1102    /// });
1103    /// # });
1104    /// ```
1105    ///
1106    /// See also: [`Ui::menu_button`] and [`Ui::close_menu`].
1107    pub fn context_menu(&self, add_contents: impl FnOnce(&mut Ui)) -> Option<InnerResponse<()>> {
1108        menu::context_menu(self, add_contents)
1109    }
1110
1111    /// Returns whether a context menu is currently open for this widget.
1112    ///
1113    /// See [`Self::context_menu`].
1114    pub fn context_menu_opened(&self) -> bool {
1115        menu::context_menu_opened(self)
1116    }
1117
1118    /// Draw a debug rectangle over the response displaying the response's id and whether it is
1119    /// enabled and/or hovered.
1120    ///
1121    /// This function is intended for debugging purpose and can be useful, for example, in case of
1122    /// widget id instability.
1123    ///
1124    /// Color code:
1125    /// - Blue: Enabled but not hovered
1126    /// - Green: Enabled and hovered
1127    /// - Red: Disabled
1128    pub fn paint_debug_info(&self) {
1129        self.ctx.debug_painter().debug_rect(
1130            self.rect,
1131            if self.hovered() {
1132                crate::Color32::DARK_GREEN
1133            } else if self.enabled() {
1134                crate::Color32::BLUE
1135            } else {
1136                crate::Color32::RED
1137            },
1138            format!("{:?}", self.id),
1139        );
1140    }
1141}
1142
1143impl Response {
1144    /// A logical "or" operation.
1145    /// For instance `a.union(b).hovered` means "was either a or b hovered?".
1146    ///
1147    /// The resulting [`Self::id`] will come from the first (`self`) argument.
1148    ///
1149    /// You may not call [`Self::interact`] on the resulting `Response`.
1150    pub fn union(&self, other: Self) -> Self {
1151        assert!(self.ctx == other.ctx);
1152        debug_assert!(
1153            self.layer_id == other.layer_id,
1154            "It makes no sense to combine Responses from two different layers"
1155        );
1156        Self {
1157            ctx: other.ctx,
1158            layer_id: self.layer_id,
1159            id: self.id,
1160            rect: self.rect.union(other.rect),
1161            interact_rect: self.interact_rect.union(other.interact_rect),
1162            sense: self.sense.union(other.sense),
1163            flags: self.flags | other.flags,
1164            interact_pointer_pos: self.interact_pointer_pos.or(other.interact_pointer_pos),
1165            intrinsic_size: None,
1166        }
1167    }
1168}
1169
1170impl Response {
1171    /// Returns a response with a modified [`Self::rect`].
1172    #[inline]
1173    pub fn with_new_rect(self, rect: Rect) -> Self {
1174        Self { rect, ..self }
1175    }
1176}
1177
1178/// See [`Response::union`].
1179///
1180/// To summarize the response from many widgets you can use this pattern:
1181///
1182/// ```
1183/// use egui::*;
1184/// fn draw_vec2(ui: &mut Ui, v: &mut Vec2) -> Response {
1185///     ui.add(DragValue::new(&mut v.x)) | ui.add(DragValue::new(&mut v.y))
1186/// }
1187/// ```
1188///
1189/// Now `draw_vec2(ui, foo).hovered` is true if either [`DragValue`](crate::DragValue) were hovered.
1190impl std::ops::BitOr for Response {
1191    type Output = Self;
1192
1193    fn bitor(self, rhs: Self) -> Self {
1194        self.union(rhs)
1195    }
1196}
1197
1198/// See [`Response::union`].
1199///
1200/// To summarize the response from many widgets you can use this pattern:
1201///
1202/// ```
1203/// # egui::__run_test_ui(|ui| {
1204/// # let (widget_a, widget_b, widget_c) = (egui::Label::new("a"), egui::Label::new("b"), egui::Label::new("c"));
1205/// let mut response = ui.add(widget_a);
1206/// response |= ui.add(widget_b);
1207/// response |= ui.add(widget_c);
1208/// if response.hovered() { ui.label("You hovered at least one of the widgets"); }
1209/// # });
1210/// ```
1211impl std::ops::BitOrAssign for Response {
1212    fn bitor_assign(&mut self, rhs: Self) {
1213        *self = self.union(rhs);
1214    }
1215}
1216
1217// ----------------------------------------------------------------------------
1218
1219/// Returned when we wrap some ui-code and want to return both
1220/// the results of the inner function and the ui as a whole, e.g.:
1221///
1222/// ```
1223/// # egui::__run_test_ui(|ui| {
1224/// let inner_resp = ui.horizontal(|ui| {
1225///     ui.label("Blah blah");
1226///     42
1227/// });
1228/// inner_resp.response.on_hover_text("You hovered the horizontal layout");
1229/// assert_eq!(inner_resp.inner, 42);
1230/// # });
1231/// ```
1232#[derive(Debug)]
1233pub struct InnerResponse<R> {
1234    /// What the user closure returned.
1235    pub inner: R,
1236
1237    /// The response of the area.
1238    pub response: Response,
1239}
1240
1241impl<R> InnerResponse<R> {
1242    #[inline]
1243    pub fn new(inner: R, response: Response) -> Self {
1244        Self { inner, response }
1245    }
1246}