1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-27 15:13:12 -04:00
Files
egui/egui/src/response.rs
2021-03-06 10:48:39 +01:00

441 lines
15 KiB
Rust

use crate::{
emath::{lerp, Align, Pos2, Rect, Vec2},
PointerButton, NUM_POINTER_BUTTONS,
};
use crate::{CtxRef, Id, LayerId, Sense, Ui};
// ----------------------------------------------------------------------------
/// The result of adding a widget to a [`Ui`].
///
/// A `Response` lets you know whether or not a widget is being hovered, clicked or dragged.
/// It also lets you easily show a tooltip on hover.
///
/// Whenever something gets added to a `Ui`, a `Response` object is returned.
/// [`ui.add`] returns a `Response`, as does [`ui.button`], and all similar shortcuts.
#[derive(Clone)]
pub struct Response {
// CONTEXT:
/// Used for optionally showing a tooltip and checking for more interactions.
pub ctx: CtxRef,
// IN:
/// Which layer the widget is part of.
pub layer_id: LayerId,
/// The `Id` of the widget/area this response pertains.
pub id: Id,
/// The area of the screen we are talking about.
pub rect: Rect,
/// The senses (click and/or drag) that the widget was interested in (if any).
pub sense: Sense,
/// Was the widget enabled?
/// If `false`, there was no interaction attempted (not even hover).
pub(crate) enabled: bool,
// OUT:
/// The pointer is hovering above this widget or the widget was clicked/tapped this frame.
pub(crate) hovered: bool,
/// The pointer clicked this thing this frame.
pub(crate) clicked: [bool; NUM_POINTER_BUTTONS],
/// The thing was double-clicked.
pub(crate) double_clicked: [bool; NUM_POINTER_BUTTONS],
/// The widgets is being dragged
pub(crate) dragged: bool,
/// The widget was being dragged, but now it has been released.
pub(crate) drag_released: bool,
/// Is the pointer button currently down on this widget?
/// This is true if the pointer is pressing down or dragging a widget
pub(crate) is_pointer_button_down_on: bool,
/// Where the pointer (mouse/touch) were when when this widget was clicked or dragged.
/// `None` if the widget is not being interacted with.
pub(crate) interact_pointer_pos: Option<Pos2>,
/// This widget has the keyboard focus (i.e. is receiving key presses).
pub(crate) has_kb_focus: bool,
/// The widget had keyboard focus and lost it.
pub(crate) lost_kb_focus: bool,
/// What the underlying data changed?
/// e.g. the slider was dragged, text was entered in a `TextEdit` etc.
/// Always `false` for something like a `Button`.
pub(crate) changed: bool,
}
impl std::fmt::Debug for Response {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self {
ctx: _,
layer_id,
id,
rect,
sense,
enabled,
hovered,
clicked,
double_clicked,
dragged,
drag_released,
is_pointer_button_down_on,
interact_pointer_pos,
has_kb_focus,
lost_kb_focus,
changed,
} = self;
f.debug_struct("Response")
.field("layer_id", layer_id)
.field("id", id)
.field("rect", rect)
.field("sense", sense)
.field("enabled", enabled)
.field("hovered", hovered)
.field("clicked", clicked)
.field("double_clicked", double_clicked)
.field("dragged", dragged)
.field("drag_released", drag_released)
.field("is_pointer_button_down_on", is_pointer_button_down_on)
.field("interact_pointer_pos", interact_pointer_pos)
.field("has_kb_focus", has_kb_focus)
.field("lost_kb_focus", lost_kb_focus)
.field("changed", changed)
.finish()
}
}
impl Response {
/// Returns true if this widget was clicked this frame by the primary button.
pub fn clicked(&self) -> bool {
self.clicked[PointerButton::Primary as usize]
}
/// Returns true if this widget was clicked this frame by the given button.
pub fn clicked_by(&self, button: PointerButton) -> bool {
self.clicked[button as usize]
}
/// Returns true if this widget was clicked this frame by the secondary mouse button (e.g. the right mouse button).
pub fn secondary_clicked(&self) -> bool {
self.clicked[PointerButton::Secondary as usize]
}
/// Returns true if this widget was clicked this frame by the middle mouse button.
pub fn middle_clicked(&self) -> bool {
self.clicked[PointerButton::Middle as usize]
}
/// Returns true if this widget was double-clicked this frame by the primary button.
pub fn double_clicked(&self) -> bool {
self.double_clicked[PointerButton::Primary as usize]
}
/// Returns true if this widget was double-clicked this frame by the given button.
pub fn double_clicked_by(&self, button: PointerButton) -> bool {
self.double_clicked[button as usize]
}
/// `true` if there was a click *outside* this widget this frame.
pub fn clicked_elsewhere(&self) -> bool {
!self.hovered && self.ctx.input().pointer.any_pressed()
}
/// Was the widget enabled?
/// If false, there was no interaction attempted
/// and the widget should be drawn in a gray disabled look.
pub fn enabled(&self) -> bool {
self.enabled
}
/// The pointer is hovering above this widget or the widget was clicked/tapped this frame.
pub fn hovered(&self) -> bool {
self.hovered
}
/// This widget has the keyboard focus (i.e. is receiving key presses).
pub fn has_kb_focus(&self) -> bool {
self.has_kb_focus
}
/// The widget had keyboard focus and lost it,
/// perhaps because the user pressed enter.
/// If you want to do an action when a user presses enter in a text field,
/// use this.
///
/// ```
/// # let mut ui = egui::Ui::__test();
/// # let mut my_text = String::new();
/// # fn do_request(_: &str) {}
/// if ui.text_edit_singleline(&mut my_text).lost_kb_focus() {
/// do_request(&my_text);
/// }
/// ```
pub fn lost_kb_focus(&self) -> bool {
self.lost_kb_focus
}
/// The widgets is being dragged.
///
/// To find out which button(s), query [`crate::PointerState::button_down`]
/// (`ui.input().pointer.button_down(…)`).
pub fn dragged(&self) -> bool {
self.dragged
}
pub fn dragged_by(&self, button: PointerButton) -> bool {
self.dragged() && self.ctx.input().pointer.button_down(button)
}
/// Did a drag on this widgets begin this frame?
pub fn drag_started(&self) -> bool {
self.dragged && self.ctx.input().pointer.any_pressed()
}
/// The widget was being dragged, but now it has been released.
pub fn drag_released(&self) -> bool {
self.drag_released
}
/// If dragged, how many points were we dragged and in what direction?
pub fn drag_delta(&self) -> Vec2 {
if self.dragged() {
self.ctx.input().pointer.delta()
} else {
Vec2::ZERO
}
}
/// Where the pointer (mouse/touch) were when when this widget was clicked or dragged.
/// `None` if the widget is not being interacted with.
pub fn interact_pointer_pos(&self) -> Option<Pos2> {
self.interact_pointer_pos
}
/// If it is a good idea to show a tooltip, where is pointer?
/// None if the pointer is outside the response area.
pub fn hover_pos(&self) -> Option<Pos2> {
if self.hovered() {
self.ctx.input().pointer.hover_pos()
} else {
None
}
}
/// Is the pointer button currently down on this widget?
/// This is true if the pointer is pressing down or dragging a widget
pub fn is_pointer_button_down_on(&self) -> bool {
self.is_pointer_button_down_on
}
/// What the underlying data changed?
///
/// e.g. the slider was dragged, text was entered in a `TextEdit` etc.
/// Always `false` for something like a `Button`.
/// Can sometimes be `true` even though the data didn't changed
/// (e.g. if the user entered a character and erased it the same frame).
pub fn changed(&self) -> bool {
self.changed
}
/// Report the data shown by this widget changed.
///
/// This must be called by widgets that represent some mutable data,
/// e.g. checkboxes, sliders etc.
pub fn mark_changed(&mut self) {
self.changed = true;
}
/// Show this UI if the item was hovered (i.e. a tooltip).
/// If you call this multiple times the tooltips will stack underneath the previous ones.
pub fn on_hover_ui(self, add_contents: impl FnOnce(&mut Ui)) -> Self {
if self.should_show_hover_ui() {
crate::containers::show_tooltip_under(
&self.ctx,
self.id.with("__tooltip"),
&self.rect,
add_contents,
);
}
self
}
fn should_show_hover_ui(&self) -> bool {
if self.ctx.memory().everything_is_visible() {
true
} else if self.hovered && self.ctx.input().pointer.has_pointer() {
let show_tooltips_only_when_still =
self.ctx.style().interaction.show_tooltips_only_when_still;
if show_tooltips_only_when_still {
if self.ctx.input().pointer.is_still() {
true
} else {
// wait for mouse to stop
self.ctx.request_repaint();
false
}
} else {
true
}
} else {
false
}
}
/// Show this text if the item was hovered (i.e. a tooltip).
/// If you call this multiple times the tooltips will stack underneath the previous ones.
pub fn on_hover_text(self, text: impl Into<String>) -> Self {
self.on_hover_ui(|ui| {
ui.add(crate::widgets::Label::new(text));
})
}
#[deprecated = "Deprecated 2020-10-01: use `on_hover_text` instead."]
pub fn tooltip_text(self, text: impl Into<String>) -> Self {
self.on_hover_text(text)
}
/// Check for more interactions (e.g. sense clicks on a `Response` returned from a label).
///
/// ```
/// # let mut ui = egui::Ui::__test();
/// let response = ui.label("hello");
/// assert!(!response.clicked()); // labels don't sense clicks
/// let response = response.interact(egui::Sense::click());
/// if response.clicked() { /* … */ }
/// ```
pub fn interact(&self, sense: Sense) -> Self {
self.ctx.interact_with_hovered(
self.layer_id,
self.id,
self.rect,
sense,
self.enabled,
self.hovered,
)
}
/// Move the scroll to this UI with the specified alignment.
///
/// ```
/// # use egui::Align;
/// # let mut ui = &mut egui::Ui::__test();
/// egui::ScrollArea::auto_sized().show(ui, |ui| {
/// for i in 0..1000 {
/// let response = ui.button(format!("Button {}", i));
/// if response.clicked() {
/// response.scroll_to_me(Align::Center);
/// }
/// }
/// });
/// ```
pub fn scroll_to_me(&self, align: Align) {
let scroll_target = lerp(self.rect.y_range(), align.to_factor());
self.ctx.frame_state().scroll_target = Some((scroll_target, align));
}
}
impl Response {
/// A logical "or" operation.
/// For instance `a.union(b).hovered` means "was either a or b hovered?".
pub fn union(&self, other: Self) -> Self {
assert!(self.ctx == other.ctx);
debug_assert_eq!(
self.layer_id, other.layer_id,
"It makes no sense to combine Responses from two different layers"
);
Self {
ctx: other.ctx,
layer_id: self.layer_id,
id: self.id,
rect: self.rect.union(other.rect),
sense: self.sense.union(other.sense),
enabled: self.enabled || other.enabled,
hovered: self.hovered || other.hovered,
clicked: [
self.clicked[0] || other.clicked[0],
self.clicked[1] || other.clicked[1],
self.clicked[2] || other.clicked[2],
],
double_clicked: [
self.double_clicked[0] || other.double_clicked[0],
self.double_clicked[1] || other.double_clicked[1],
self.double_clicked[2] || other.double_clicked[2],
],
dragged: self.dragged || other.dragged,
drag_released: self.drag_released || other.drag_released,
is_pointer_button_down_on: self.is_pointer_button_down_on
|| other.is_pointer_button_down_on,
interact_pointer_pos: self.interact_pointer_pos.or(other.interact_pointer_pos),
has_kb_focus: self.has_kb_focus || other.has_kb_focus,
lost_kb_focus: self.lost_kb_focus || other.lost_kb_focus,
changed: self.changed || other.changed,
}
}
}
/// To summarize the response from many widgets you can use this pattern:
///
/// ```
/// use egui::*;
/// fn draw_vec2(ui: &mut Ui, v: &mut Vec2) -> Response {
/// ui.add(DragValue::f32(&mut v.x)) | ui.add(DragValue::f32(&mut v.y))
/// }
/// ```
///
/// Now `draw_vec2(ui, foo).hovered` is true if either `DragValue` were hovered.
impl std::ops::BitOr for Response {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
self.union(rhs)
}
}
/// To summarize the response from many widgets you can use this pattern:
///
/// ```
/// # let mut ui = egui::Ui::__test();
/// # let (widget_a, widget_b, widget_c) = (egui::Label::new("a"), egui::Label::new("b"), egui::Label::new("c"));
/// let mut response = ui.add(widget_a);
/// response |= ui.add(widget_b);
/// response |= ui.add(widget_c);
/// if response.hovered() { ui.label("You hovered at least one of the widgets"); }
/// ```
impl std::ops::BitOrAssign for Response {
fn bitor_assign(&mut self, rhs: Self) {
*self = self.union(rhs);
}
}
// ----------------------------------------------------------------------------
/// Returned when we wrap some ui-code and want to return both
/// the results of the inner function and the ui as a whole, e.g.:
///
/// ```
/// # let ui = &mut egui::Ui::__test();
/// let inner_resp = ui.horizontal(|ui| {
/// ui.label("Blah blah");
/// 42
/// });
/// inner_resp.response.on_hover_text("You hovered the horizontal layout");
/// assert_eq!(inner_resp.inner, 42);
/// ```
pub struct InnerResponse<R> {
pub inner: R,
pub response: Response,
}
impl<R> InnerResponse<R> {
pub fn new(inner: R, response: Response) -> Self {
Self { inner, response }
}
}