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