Improve `Response.dragged`, `drag_started` and `clicked` (#3888)
If a widgets sense both clicks and drags, we don't know wether or not a mouse press on it will be a short click or a long drag. With this PR, `response.dragged` and `response.drag_started` isn't true until we know it is a drag and not a click. If the widget ONLY senses drags, then we know as soon as someone presses on it that it is a drag. If it is sensitive to both clicks and drags, we don't know until the mouse moves a bit, or stays pressed down long enough. This PR also ensures that `response.clicked` and is only true for widgets that senses clicks.
This commit is contained in:
parent
d190df7d25
commit
a815923717
|
|
@ -718,9 +718,9 @@ impl TopBottomPanel {
|
|||
if ui.input(|i| i.pointer.any_pressed() && i.pointer.any_down())
|
||||
&& mouse_over_resize_line
|
||||
{
|
||||
ui.memory_mut(|mem| mem.interaction_mut().drag_id = Some(resize_id));
|
||||
ui.memory_mut(|mem| mem.set_dragged_id(resize_id));
|
||||
}
|
||||
is_resizing = ui.memory(|mem| mem.interaction().drag_id == Some(resize_id));
|
||||
is_resizing = ui.memory(|mem| mem.is_being_dragged(resize_id));
|
||||
if is_resizing {
|
||||
let height = (pointer.y - side.side_y(panel_rect)).abs();
|
||||
let height =
|
||||
|
|
|
|||
|
|
@ -726,12 +726,8 @@ fn window_interaction(
|
|||
id: Id,
|
||||
rect: Rect,
|
||||
) -> Option<WindowInteraction> {
|
||||
{
|
||||
let drag_id = ctx.memory(|mem| mem.interaction().drag_id);
|
||||
|
||||
if drag_id.is_some() && drag_id != Some(id) {
|
||||
return None;
|
||||
}
|
||||
if ctx.memory(|mem| mem.dragging_something_else(id)) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut window_interaction = ctx.memory(|mem| mem.window_interaction());
|
||||
|
|
|
|||
|
|
@ -956,11 +956,8 @@ impl Context {
|
|||
enabled: bool,
|
||||
contains_pointer: bool,
|
||||
) -> Response {
|
||||
let hovered = contains_pointer && enabled; // can't even hover disabled widgets
|
||||
|
||||
let highlighted = self.frame_state(|fs| fs.highlight_this_frame.contains(&id));
|
||||
|
||||
let mut response = Response {
|
||||
// This is the start - we'll fill in the fields below:
|
||||
let mut res = Response {
|
||||
ctx: self.clone(),
|
||||
layer_id,
|
||||
id,
|
||||
|
|
@ -968,11 +965,12 @@ impl Context {
|
|||
sense,
|
||||
enabled,
|
||||
contains_pointer,
|
||||
hovered,
|
||||
highlighted,
|
||||
hovered: contains_pointer && enabled,
|
||||
highlighted: self.frame_state(|fs| fs.highlight_this_frame.contains(&id)),
|
||||
clicked: Default::default(),
|
||||
double_clicked: Default::default(),
|
||||
triple_clicked: Default::default(),
|
||||
drag_started: false,
|
||||
dragged: false,
|
||||
drag_released: false,
|
||||
is_pointer_button_down_on: false,
|
||||
|
|
@ -994,10 +992,10 @@ impl Context {
|
|||
// Make sure anything that can receive focus has an AccessKit node.
|
||||
// TODO(mwcampbell): For nodes that are filled from widget info,
|
||||
// some information is written to the node twice.
|
||||
self.accesskit_node_builder(id, |builder| response.fill_accesskit_node_common(builder));
|
||||
self.accesskit_node_builder(id, |builder| res.fill_accesskit_node_common(builder));
|
||||
}
|
||||
|
||||
let clicked_elsewhere = response.clicked_elsewhere();
|
||||
let clicked_elsewhere = res.clicked_elsewhere();
|
||||
self.write(|ctx| {
|
||||
let input = &ctx.viewports.entry(ctx.viewport_id()).or_default().input;
|
||||
let memory = &mut ctx.memory;
|
||||
|
|
@ -1007,41 +1005,53 @@ impl Context {
|
|||
}
|
||||
|
||||
if sense.click
|
||||
&& memory.has_focus(response.id)
|
||||
&& memory.has_focus(res.id)
|
||||
&& (input.key_pressed(Key::Space) || input.key_pressed(Key::Enter))
|
||||
{
|
||||
// Space/enter works like a primary click for e.g. selected buttons
|
||||
response.clicked[PointerButton::Primary as usize] = true;
|
||||
res.clicked[PointerButton::Primary as usize] = true;
|
||||
}
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
if sense.click
|
||||
&& input.has_accesskit_action_request(response.id, accesskit::Action::Default)
|
||||
if sense.click && input.has_accesskit_action_request(res.id, accesskit::Action::Default)
|
||||
{
|
||||
response.clicked[PointerButton::Primary as usize] = true;
|
||||
res.clicked[PointerButton::Primary as usize] = true;
|
||||
}
|
||||
|
||||
if sense.click || sense.drag {
|
||||
let interaction = memory.interaction_mut();
|
||||
|
||||
interaction.click_interest |= hovered && sense.click;
|
||||
interaction.drag_interest |= hovered && sense.drag;
|
||||
interaction.click_interest |= contains_pointer && sense.click;
|
||||
interaction.drag_interest |= contains_pointer && sense.drag;
|
||||
|
||||
response.dragged = interaction.drag_id == Some(id);
|
||||
response.is_pointer_button_down_on =
|
||||
interaction.click_id == Some(id) || response.dragged;
|
||||
res.is_pointer_button_down_on =
|
||||
interaction.click_id == Some(id) || interaction.drag_id == Some(id);
|
||||
|
||||
if sense.click && sense.drag {
|
||||
// This widget is sensitive to both clicks and drags.
|
||||
// When the mouse first is pressed, it could be either,
|
||||
// so we postpone the decision until we know.
|
||||
res.dragged =
|
||||
interaction.drag_id == Some(id) && input.pointer.is_decidedly_dragging();
|
||||
res.drag_started = res.dragged && input.pointer.started_decidedly_dragging;
|
||||
} else if sense.drag {
|
||||
// We are just sensitive to drags, so we can mark ourself as dragged right away:
|
||||
res.dragged = interaction.drag_id == Some(id);
|
||||
// res.drag_started will be filled below if applicable
|
||||
}
|
||||
|
||||
for pointer_event in &input.pointer.pointer_events {
|
||||
match pointer_event {
|
||||
PointerEvent::Moved(_) => {}
|
||||
|
||||
PointerEvent::Pressed { .. } => {
|
||||
if hovered {
|
||||
if contains_pointer {
|
||||
let interaction = memory.interaction_mut();
|
||||
|
||||
if sense.click && interaction.click_id.is_none() {
|
||||
// potential start of a click
|
||||
interaction.click_id = Some(id);
|
||||
response.is_pointer_button_down_on = true;
|
||||
res.is_pointer_button_down_on = true;
|
||||
}
|
||||
|
||||
// HACK: windows have low priority on dragging.
|
||||
|
|
@ -1056,51 +1066,62 @@ impl Context {
|
|||
interaction.drag_id = Some(id);
|
||||
interaction.drag_is_window = false;
|
||||
memory.set_window_interaction(None); // HACK: stop moving windows (if any)
|
||||
response.is_pointer_button_down_on = true;
|
||||
response.dragged = true;
|
||||
|
||||
res.is_pointer_button_down_on = true;
|
||||
|
||||
// Again, only if we are ONLY sensitive to drags can we decide that this is a drag now.
|
||||
if sense.click {
|
||||
res.dragged = false;
|
||||
res.drag_started = false;
|
||||
} else {
|
||||
res.dragged = true;
|
||||
res.drag_started = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PointerEvent::Released { click, button } => {
|
||||
response.drag_released = response.dragged;
|
||||
response.dragged = false;
|
||||
|
||||
if hovered && response.is_pointer_button_down_on {
|
||||
PointerEvent::Released { click, button } => {
|
||||
res.drag_released = res.dragged;
|
||||
res.dragged = false;
|
||||
|
||||
if sense.click && res.hovered && res.is_pointer_button_down_on {
|
||||
if let Some(click) = click {
|
||||
let clicked = hovered && response.is_pointer_button_down_on;
|
||||
response.clicked[*button as usize] = clicked;
|
||||
response.double_clicked[*button as usize] =
|
||||
let clicked = res.hovered && res.is_pointer_button_down_on;
|
||||
res.clicked[*button as usize] = clicked;
|
||||
res.double_clicked[*button as usize] =
|
||||
clicked && click.is_double();
|
||||
response.triple_clicked[*button as usize] =
|
||||
res.triple_clicked[*button as usize] =
|
||||
clicked && click.is_triple();
|
||||
}
|
||||
}
|
||||
response.is_pointer_button_down_on = false;
|
||||
|
||||
res.is_pointer_button_down_on = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if response.is_pointer_button_down_on {
|
||||
response.interact_pointer_pos = input.pointer.interact_pos();
|
||||
if res.is_pointer_button_down_on {
|
||||
res.interact_pointer_pos = input.pointer.interact_pos();
|
||||
}
|
||||
|
||||
if input.pointer.any_down() && !response.is_pointer_button_down_on {
|
||||
if input.pointer.any_down() && !res.is_pointer_button_down_on {
|
||||
// We don't hover widgets while interacting with *other* widgets:
|
||||
response.hovered = false;
|
||||
res.hovered = false;
|
||||
}
|
||||
|
||||
if memory.has_focus(response.id) && clicked_elsewhere {
|
||||
if memory.has_focus(res.id) && clicked_elsewhere {
|
||||
memory.surrender_focus(id);
|
||||
}
|
||||
|
||||
if response.dragged() && !memory.has_focus(response.id) {
|
||||
if res.dragged() && !memory.has_focus(res.id) {
|
||||
// e.g.: remove focus from a widget when you drag something else
|
||||
memory.stop_text_input();
|
||||
}
|
||||
});
|
||||
|
||||
response
|
||||
res
|
||||
}
|
||||
|
||||
/// Get a full-screen painter for a new or existing layer
|
||||
|
|
|
|||
|
|
@ -638,6 +638,9 @@ pub struct PointerState {
|
|||
/// for it to be registered as a click.
|
||||
pub(crate) has_moved_too_much_for_a_click: bool,
|
||||
|
||||
/// Did [`Self::is_decidedly_dragging`] go from `false` to `true` this frame?
|
||||
pub(crate) started_decidedly_dragging: bool,
|
||||
|
||||
/// When did the pointer get click last?
|
||||
/// Used to check for double-clicks.
|
||||
last_click_time: f64,
|
||||
|
|
@ -667,6 +670,7 @@ impl Default for PointerState {
|
|||
press_origin: None,
|
||||
press_start_time: None,
|
||||
has_moved_too_much_for_a_click: false,
|
||||
started_decidedly_dragging: false,
|
||||
last_click_time: std::f64::NEG_INFINITY,
|
||||
last_last_click_time: std::f64::NEG_INFINITY,
|
||||
last_move_time: std::f64::NEG_INFINITY,
|
||||
|
|
@ -678,6 +682,8 @@ impl Default for PointerState {
|
|||
impl PointerState {
|
||||
#[must_use]
|
||||
pub(crate) fn begin_frame(mut self, time: f64, new: &RawInput) -> Self {
|
||||
let was_decidedly_dragging = self.is_decidedly_dragging();
|
||||
|
||||
self.time = time;
|
||||
|
||||
self.pointer_events.clear();
|
||||
|
|
@ -798,6 +804,8 @@ impl PointerState {
|
|||
self.last_move_time = time;
|
||||
}
|
||||
|
||||
self.started_decidedly_dragging = self.is_decidedly_dragging() && !was_decidedly_dragging;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -1137,6 +1145,7 @@ impl PointerState {
|
|||
press_origin,
|
||||
press_start_time,
|
||||
has_moved_too_much_for_a_click,
|
||||
started_decidedly_dragging,
|
||||
last_click_time,
|
||||
last_last_click_time,
|
||||
pointer_events,
|
||||
|
|
@ -1156,6 +1165,9 @@ impl PointerState {
|
|||
ui.label(format!(
|
||||
"has_moved_too_much_for_a_click: {has_moved_too_much_for_a_click}"
|
||||
));
|
||||
ui.label(format!(
|
||||
"started_decidedly_dragging: {started_decidedly_dragging}"
|
||||
));
|
||||
ui.label(format!("last_click_time: {last_click_time:#?}"));
|
||||
ui.label(format!("last_last_click_time: {last_last_click_time:#?}"));
|
||||
ui.label(format!("last_move_time: {last_move_time:#?}"));
|
||||
|
|
|
|||
|
|
@ -238,6 +238,11 @@ pub(crate) struct Interaction {
|
|||
pub click_id: Option<Id>,
|
||||
|
||||
/// A widget interested in drags that has a mouse press on it.
|
||||
///
|
||||
/// Note that this is set as soon as the mouse is pressed,
|
||||
/// so the widget may not yet be marked as "dragged",
|
||||
/// as that can only happen after the mouse has moved a bit
|
||||
/// (at least if the widget is interesated in both clicks and drags).
|
||||
pub drag_id: Option<Id>,
|
||||
|
||||
pub focus: Focus,
|
||||
|
|
@ -698,12 +703,22 @@ impl Memory {
|
|||
}
|
||||
|
||||
/// Is this specific widget being dragged?
|
||||
///
|
||||
/// Usually it is better to use [`crate::Response::dragged`].
|
||||
///
|
||||
/// A widget that sense both clicks and drags is only marked as "dragged"
|
||||
/// when the mouse has moved a bit, but `is_being_dragged` will return true immediately.
|
||||
#[inline(always)]
|
||||
pub fn is_being_dragged(&self, id: Id) -> bool {
|
||||
self.interaction().drag_id == Some(id)
|
||||
}
|
||||
|
||||
/// Get the id of the widget being dragged, if any.
|
||||
///
|
||||
/// Note that this is set as soon as the mouse is pressed,
|
||||
/// so the widget may not yet be marked as "dragged",
|
||||
/// as that can only happen after the mouse has moved a bit
|
||||
/// (at least if the widget is interesated in both clicks and drags).
|
||||
#[inline(always)]
|
||||
pub fn dragged_id(&self) -> Option<Id> {
|
||||
self.interaction().drag_id
|
||||
|
|
@ -721,6 +736,15 @@ impl Memory {
|
|||
self.interaction_mut().drag_id = None;
|
||||
}
|
||||
|
||||
/// Is something else being dragged?
|
||||
///
|
||||
/// Returns true if we are dragging something, but not the given widget.
|
||||
#[inline(always)]
|
||||
pub fn dragging_something_else(&self, not_this: Id) -> bool {
|
||||
let drag_id = self.interaction().drag_id;
|
||||
drag_id.is_some() && drag_id != Some(not_this)
|
||||
}
|
||||
|
||||
/// Forget window positions, sizes etc.
|
||||
/// Can be used to auto-layout windows.
|
||||
pub fn reset_areas(&mut self) {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ use crate::{
|
|||
/// 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.
|
||||
// TODO(emilk): we should be using bit sets instead of so many bools
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Response {
|
||||
// CONTEXT:
|
||||
/// Used for optionally showing a tooltip and checking for more interactions.
|
||||
|
|
@ -64,7 +64,11 @@ pub struct Response {
|
|||
#[doc(hidden)]
|
||||
pub triple_clicked: [bool; NUM_POINTER_BUTTONS],
|
||||
|
||||
/// The widgets is being dragged
|
||||
/// The widget started being dragged this frame.
|
||||
#[doc(hidden)]
|
||||
pub drag_started: bool,
|
||||
|
||||
/// The widgets is being dragged.
|
||||
#[doc(hidden)]
|
||||
pub dragged: bool,
|
||||
|
||||
|
|
@ -90,48 +94,6 @@ pub struct Response {
|
|||
pub 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,
|
||||
contains_pointer,
|
||||
hovered,
|
||||
highlighted,
|
||||
clicked,
|
||||
double_clicked,
|
||||
triple_clicked,
|
||||
dragged,
|
||||
drag_released,
|
||||
is_pointer_button_down_on,
|
||||
interact_pointer_pos,
|
||||
changed,
|
||||
} = self;
|
||||
f.debug_struct("Response")
|
||||
.field("layer_id", layer_id)
|
||||
.field("id", id)
|
||||
.field("rect", rect)
|
||||
.field("sense", sense)
|
||||
.field("enabled", enabled)
|
||||
.field("contains_pointer", contains_pointer)
|
||||
.field("hovered", hovered)
|
||||
.field("highlighted", highlighted)
|
||||
.field("clicked", clicked)
|
||||
.field("double_clicked", double_clicked)
|
||||
.field("triple_clicked", triple_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("changed", changed)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Response {
|
||||
/// Returns true if this widget was clicked this frame by the primary button.
|
||||
///
|
||||
|
|
@ -295,45 +257,50 @@ impl Response {
|
|||
self.ctx.memory_mut(|mem| mem.surrender_focus(self.id));
|
||||
}
|
||||
|
||||
/// Did a drag on this widgets begin this frame?
|
||||
///
|
||||
/// This is only true if the widget sense drags.
|
||||
/// If the widget also senses clicks, this will only become true if the pointer has moved a bit.
|
||||
///
|
||||
/// This will only be true for a single frame.
|
||||
#[inline]
|
||||
pub fn drag_started(&self) -> bool {
|
||||
self.drag_started
|
||||
}
|
||||
|
||||
/// Did a drag on this widgets by the button begin this frame?
|
||||
///
|
||||
/// This is only true if the widget sense drags.
|
||||
/// If the widget also senses clicks, this will only become true if the pointer has moved a bit.
|
||||
///
|
||||
/// This will only be true for a single frame.
|
||||
#[inline]
|
||||
pub fn drag_started_by(&self, button: PointerButton) -> bool {
|
||||
self.drag_started() && self.ctx.input(|i| i.pointer.button_down(button))
|
||||
}
|
||||
|
||||
/// The widgets is being dragged.
|
||||
///
|
||||
/// To find out which button(s), query [`crate::PointerState::button_down`]
|
||||
/// (`ui.input(|i| i.pointer.button_down(…))`).
|
||||
/// To find out which button(s), use [`Self::dragged_by`].
|
||||
///
|
||||
/// Note that the widget must be sensing drags with [`Sense::drag`].
|
||||
/// If the widget is only sensitive to drags, this is `true` as soon as the pointer presses down on it.
|
||||
/// If the widget is also sensitive to drags, this won't be true until the pointer has moved a bit,
|
||||
/// or the user has pressed down for long enough.
|
||||
/// See [`crate::input_state::PointerState::is_decidedly_dragging`] for details.
|
||||
///
|
||||
/// If the widget is NOT sensitive to drags, this will always be `false`.
|
||||
/// [`crate::DragValue`] senses drags; [`crate::Label`] does not (unless you call [`crate::Label::sense`]).
|
||||
///
|
||||
/// You can use [`Self::interact`] to sense more things *after* adding a widget.
|
||||
#[inline(always)]
|
||||
pub fn dragged(&self) -> bool {
|
||||
self.dragged
|
||||
}
|
||||
|
||||
/// The Widget is being decidedly dragged.
|
||||
///
|
||||
/// This helper function checks both the output of [`Self::dragged`] and [`crate::PointerState::is_decidedly_dragging`].
|
||||
#[inline]
|
||||
pub fn decidedly_dragged(&self) -> bool {
|
||||
self.dragged() && self.ctx.input(|i| i.pointer.is_decidedly_dragging())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn dragged_by(&self, button: PointerButton) -> bool {
|
||||
self.dragged() && self.ctx.input(|i| i.pointer.button_down(button))
|
||||
}
|
||||
|
||||
/// Did a drag on this widgets begin this frame?
|
||||
#[inline]
|
||||
pub fn drag_started(&self) -> bool {
|
||||
self.dragged && self.ctx.input(|i| i.pointer.any_pressed())
|
||||
}
|
||||
|
||||
/// Did a drag on this widgets by the button begin this frame?
|
||||
#[inline]
|
||||
pub fn drag_started_by(&self, button: PointerButton) -> bool {
|
||||
self.drag_started() && self.ctx.input(|i| i.pointer.button_pressed(button))
|
||||
}
|
||||
|
||||
/// The widget was being dragged, but now it has been released.
|
||||
#[inline]
|
||||
pub fn drag_released(&self) -> bool {
|
||||
|
|
@ -356,6 +323,7 @@ impl Response {
|
|||
}
|
||||
|
||||
/// Where the pointer (mouse/touch) were when when this widget was clicked or dragged.
|
||||
///
|
||||
/// `None` if the widget is not being interacted with.
|
||||
#[inline]
|
||||
pub fn interact_pointer_pos(&self) -> Option<Pos2> {
|
||||
|
|
@ -363,6 +331,7 @@ impl Response {
|
|||
}
|
||||
|
||||
/// If it is a good idea to show a tooltip, where is pointer?
|
||||
///
|
||||
/// None if the pointer is outside the response area.
|
||||
#[inline]
|
||||
pub fn hover_pos(&self) -> Option<Pos2> {
|
||||
|
|
@ -374,7 +343,11 @@ impl Response {
|
|||
}
|
||||
|
||||
/// Is the pointer button currently down on this widget?
|
||||
/// This is true if the pointer is pressing down or dragging a widget
|
||||
///
|
||||
/// This is true if the pointer is pressing down or dragging a widget,
|
||||
/// even when dragging outside the widget.
|
||||
///
|
||||
/// This could also be thought of as "is this widget being interacted with?".
|
||||
#[inline(always)]
|
||||
pub fn is_pointer_button_down_on(&self) -> bool {
|
||||
self.is_pointer_button_down_on
|
||||
|
|
@ -793,6 +766,7 @@ impl Response {
|
|||
self.triple_clicked[3] || other.triple_clicked[3],
|
||||
self.triple_clicked[4] || other.triple_clicked[4],
|
||||
],
|
||||
drag_started: self.drag_started || other.drag_started,
|
||||
dragged: self.dragged || other.dragged,
|
||||
drag_released: self.drag_released || other.drag_released,
|
||||
is_pointer_button_down_on: self.is_pointer_button_down_on
|
||||
|
|
|
|||
|
|
@ -306,10 +306,58 @@ impl super::View for TableTest {
|
|||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
struct HistoryEntry {
|
||||
text: String,
|
||||
repeated: usize,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct DeduplicatedHistory {
|
||||
history: std::collections::VecDeque<HistoryEntry>,
|
||||
}
|
||||
|
||||
impl DeduplicatedHistory {
|
||||
fn add(&mut self, text: String) {
|
||||
if let Some(entry) = self.history.back_mut() {
|
||||
if entry.text == text {
|
||||
entry.repeated += 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.history.push_back(HistoryEntry { text, repeated: 1 });
|
||||
if self.history.len() > 100 {
|
||||
self.history.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
fn ui(&self, ui: &mut egui::Ui) {
|
||||
egui::ScrollArea::vertical()
|
||||
.auto_shrink(false)
|
||||
.show(ui, |ui| {
|
||||
ui.spacing_mut().item_spacing.y = 4.0;
|
||||
for HistoryEntry { text, repeated } in self.history.iter().rev() {
|
||||
ui.horizontal(|ui| {
|
||||
if text.is_empty() {
|
||||
ui.weak("(empty)");
|
||||
} else {
|
||||
ui.label(text);
|
||||
}
|
||||
if 1 < *repeated {
|
||||
ui.weak(format!(" x{repeated}"));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[derive(Default)]
|
||||
pub struct InputTest {
|
||||
info: String,
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
history: [DeduplicatedHistory; 4],
|
||||
|
||||
show_hovers: bool,
|
||||
}
|
||||
|
||||
impl super::Demo for InputTest {
|
||||
|
|
@ -319,8 +367,10 @@ impl super::Demo for InputTest {
|
|||
|
||||
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
|
||||
egui::Window::new(self.name())
|
||||
.default_width(800.0)
|
||||
.open(open)
|
||||
.resizable(false)
|
||||
.resizable(true)
|
||||
.scroll2(false)
|
||||
.show(ctx, |ui| {
|
||||
use super::View as _;
|
||||
self.ui(ui);
|
||||
|
|
@ -330,52 +380,102 @@ impl super::Demo for InputTest {
|
|||
|
||||
impl super::View for InputTest {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.spacing_mut().item_spacing.y = 8.0;
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add(crate::egui_github_link_file!());
|
||||
});
|
||||
|
||||
let response = ui.add(
|
||||
egui::Button::new("Click, double-click, triple-click or drag me with any mouse button")
|
||||
.sense(egui::Sense::click_and_drag()),
|
||||
);
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("Clear").clicked() {
|
||||
*self = Default::default();
|
||||
}
|
||||
|
||||
let mut new_info = String::new();
|
||||
for &button in &[
|
||||
egui::PointerButton::Primary,
|
||||
egui::PointerButton::Secondary,
|
||||
egui::PointerButton::Middle,
|
||||
egui::PointerButton::Extra1,
|
||||
egui::PointerButton::Extra2,
|
||||
] {
|
||||
use std::fmt::Write as _;
|
||||
ui.checkbox(&mut self.show_hovers, "Show hover state");
|
||||
});
|
||||
|
||||
if response.clicked_by(button) {
|
||||
writeln!(new_info, "Clicked by {button:?} button").ok();
|
||||
}
|
||||
if response.double_clicked_by(button) {
|
||||
writeln!(new_info, "Double-clicked by {button:?} button").ok();
|
||||
}
|
||||
if response.triple_clicked_by(button) {
|
||||
writeln!(new_info, "Triple-clicked by {button:?} button").ok();
|
||||
}
|
||||
if response.dragged_by(button) {
|
||||
writeln!(
|
||||
new_info,
|
||||
"Dragged by {:?} button, delta: {:?}",
|
||||
button,
|
||||
response.drag_delta()
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
if !new_info.is_empty() {
|
||||
self.info = new_info;
|
||||
}
|
||||
ui.label("This tests how egui::Response reports events.\n\
|
||||
The different buttons are sensitive to different things.\n\
|
||||
Try interacting with them with any mouse button by clicking, double-clicking, triple-clicking, or dragging them.");
|
||||
|
||||
ui.label(&self.info);
|
||||
ui.columns(4, |columns| {
|
||||
for (i, (sense_name, sense)) in [
|
||||
("Sense::hover", egui::Sense::hover()),
|
||||
("Sense::click", egui::Sense::click()),
|
||||
("Sense::drag", egui::Sense::drag()),
|
||||
("Sense::click_and_drag", egui::Sense::click_and_drag()),
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
columns[i].push_id(i, |ui| {
|
||||
let response = ui.add(egui::Button::new(sense_name).sense(sense));
|
||||
let info = response_summary(&response, self.show_hovers);
|
||||
self.history[i].add(info.trim().to_owned());
|
||||
self.history[i].ui(ui);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn response_summary(response: &egui::Response, show_hovers: bool) -> String {
|
||||
use std::fmt::Write as _;
|
||||
|
||||
let mut new_info = String::new();
|
||||
|
||||
if show_hovers {
|
||||
if response.hovered() {
|
||||
writeln!(new_info, "hovered").ok();
|
||||
}
|
||||
if response.contains_pointer() {
|
||||
writeln!(new_info, "contains_pointer").ok();
|
||||
}
|
||||
if response.is_pointer_button_down_on() {
|
||||
writeln!(new_info, "pointer_down_on").ok();
|
||||
}
|
||||
}
|
||||
|
||||
for &button in &[
|
||||
egui::PointerButton::Primary,
|
||||
egui::PointerButton::Secondary,
|
||||
egui::PointerButton::Middle,
|
||||
egui::PointerButton::Extra1,
|
||||
egui::PointerButton::Extra2,
|
||||
] {
|
||||
let button_suffix = if button == egui::PointerButton::Primary {
|
||||
// Reduce visual clutter in common case:
|
||||
String::default()
|
||||
} else {
|
||||
format!(" by {button:?} button")
|
||||
};
|
||||
|
||||
// These are in inverse logical/chonological order, because we show them in the ui that way:
|
||||
|
||||
if response.triple_clicked_by(button) {
|
||||
writeln!(new_info, "Triple-clicked{button_suffix}").ok();
|
||||
}
|
||||
if response.double_clicked_by(button) {
|
||||
writeln!(new_info, "Double-clicked{button_suffix}").ok();
|
||||
}
|
||||
if response.clicked_by(button) {
|
||||
writeln!(new_info, "Clicked{button_suffix}").ok();
|
||||
}
|
||||
|
||||
if response.drag_released_by(button) {
|
||||
writeln!(new_info, "Drag ended{button_suffix}").ok();
|
||||
}
|
||||
if response.dragged_by(button) {
|
||||
writeln!(new_info, "Dragged{button_suffix}").ok();
|
||||
}
|
||||
if response.drag_started_by(button) {
|
||||
writeln!(new_info, "Drag started{button_suffix}").ok();
|
||||
}
|
||||
}
|
||||
|
||||
new_info
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
pub struct WindowResizeTest {
|
||||
|
|
|
|||
Loading…
Reference in New Issue