mirror of
https://github.com/emilk/egui.git
synced 2026-09-01 06:10:06 -04:00
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:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user