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}