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