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