1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-31 05:40:03 -04:00

Multiple mouse button support (#135)

Add support for primary, secondary and middle mouse buttons. Also improve ability to click things in low FPS situations.

This introduces a lot of breaking changes:

    Backends/integrations now pass mouse events via the even stream.
    Response has an interface of mostly methods instead of public members.
    input.mouse is now input.pointer and has new interface.


* Rename 'mouse' to 'pointer' everywhere (pointer = mouse or touch)

* Make Response::clicked and Response::double_clicked into methods

* Remove Response::active and add dragged() and interact_pointer_pos()

* Support multiple mouse buttons

* Make PointerState interface all methods

* Make most members of Response private
This commit is contained in:
Emil Ernerfeldt
2021-01-25 18:50:19 +01:00
committed by GitHub
parent 18b9214575
commit 247026149c
47 changed files with 1052 additions and 580 deletions

View File

@@ -57,7 +57,7 @@ impl DemoWindow {
ui.columns(self.num_columns, |cols| {
for (i, col) in cols.iter_mut().enumerate() {
col.label(format!("Column {} out of {}", i + 1, self.num_columns));
if i + 1 == self.num_columns && col.button("Delete this").clicked {
if i + 1 == self.num_columns && col.button("Delete this").clicked() {
self.num_columns -= 1;
}
}
@@ -287,15 +287,15 @@ impl LayoutDemo {
pub fn content_ui(&mut self, ui: &mut Ui) {
ui.horizontal(|ui| {
if ui.button("Top-down").clicked {
if ui.button("Top-down").clicked() {
*self = Default::default();
}
if ui.button("Top-down, centered and justified").clicked {
if ui.button("Top-down, centered and justified").clicked() {
*self = Default::default();
self.cross_align = Align::Center;
self.cross_justify = true;
}
if ui.button("Horizontal wrapped").clicked {
if ui.button("Horizontal wrapped").clicked() {
*self = Default::default();
self.main_dir = Direction::LeftToRight;
self.cross_align = Align::Center;
@@ -391,7 +391,7 @@ impl Tree {
if depth > 0
&& ui
.add(Button::new("delete").text_color(Color32::RED))
.clicked
.clicked()
{
return Action::Delete;
}
@@ -409,7 +409,7 @@ impl Tree {
})
.collect();
if ui.button("+").clicked {
if ui.button("+").clicked() {
self.0.push(Tree::default());
}

View File

@@ -14,6 +14,7 @@ impl Default for Demos {
fn default() -> Self {
let demos: Vec<Box<dyn super::Demo>> = vec![
Box::new(super::WidgetGallery::default()),
Box::new(super::input_test::InputTest::default()),
Box::new(super::FontBook::default()),
Box::new(super::Painting::default()),
Box::new(super::DancingStrings::default()),
@@ -86,7 +87,7 @@ impl DemoWindows {
ui.separator();
if ui.button("Organize windows").clicked {
if ui.button("Organize windows").clicked() {
ui.ctx().memory().reset_areas();
}
});
@@ -255,13 +256,13 @@ fn show_menu_bar(ui: &mut Ui) {
menu::bar(ui, |ui| {
menu::menu(ui, "File", |ui| {
if ui.button("Organize windows").clicked {
if ui.button("Organize windows").clicked() {
ui.ctx().memory().reset_areas();
}
if ui
.button("Clear egui memory")
.on_hover_text("Forget scroll, collapsing headers etc")
.clicked
.clicked()
{
*ui.ctx().memory() = Default::default();
}

View File

@@ -8,7 +8,7 @@ pub fn drag_source(ui: &mut Ui, id: Id, body: impl FnOnce(&mut Ui)) {
// Check for drags:
let response = ui.interact(response.rect, id, Sense::drag());
if response.hovered {
if response.hovered() {
ui.output().cursor_icon = CursorIcon::Grab;
}
} else {
@@ -25,8 +25,8 @@ pub fn drag_source(ui: &mut Ui, id: Id, body: impl FnOnce(&mut Ui)) {
// (anything with `Order::Tooltip` always gets an empty `Response`)
// So this is fine!
if let Some(mouse_pos) = ui.input().mouse.pos {
let delta = mouse_pos - response.rect.center();
if let Some(pointer_pos) = ui.input().pointer.interact_pos() {
let delta = pointer_pos - response.rect.center();
ui.ctx().translate_layer(layer_id, delta);
}
}
@@ -49,7 +49,7 @@ pub fn drop_target<R>(
let outer_rect = Rect::from_min_max(outer_rect_bounds.min, content_ui.min_rect().max + margin);
let (rect, response) = ui.allocate_at_least(outer_rect.size(), Sense::hover());
let style = if is_being_dragged && can_accept_what_is_being_dragged && response.hovered {
let style = if is_being_dragged && can_accept_what_is_being_dragged && response.hovered() {
ui.style().visuals.widgets.active
} else if is_being_dragged && can_accept_what_is_being_dragged {
ui.style().visuals.widgets.inactive
@@ -126,8 +126,7 @@ impl super::View for DragAndDropDemo {
ui.label(item);
});
let this_item_being_dragged = ui.memory().is_being_dragged(item_id);
if this_item_being_dragged {
if ui.memory().is_being_dragged(item_id) {
source_col_row = Some((col_idx, row_idx));
}
}
@@ -135,7 +134,7 @@ impl super::View for DragAndDropDemo {
.1;
let is_being_dragged = ui.memory().is_anything_being_dragged();
if is_being_dragged && can_accept_what_is_being_dragged && response.hovered {
if is_being_dragged && can_accept_what_is_being_dragged && response.hovered() {
drop_col = Some(col_idx);
}
}
@@ -143,7 +142,7 @@ impl super::View for DragAndDropDemo {
if let Some((source_col, source_row)) = source_col_row {
if let Some(drop_col) = drop_col {
if ui.input().mouse.released {
if ui.input().pointer.any_released() {
// do the drop:
let item = self.columns[source_col].remove(source_row);
self.columns[drop_col].push(item);

View File

@@ -31,7 +31,7 @@ impl FontBook {
ui.label(format!("{}\nU+{:X}\n\nClick to copy", name, chr as u32));
};
if ui.add(button).on_hover_ui(tooltip_ui).clicked {
if ui.add(button).on_hover_ui(tooltip_ui).clicked() {
ui.output().copied_text = chr.to_string();
}
}
@@ -81,7 +81,7 @@ impl super::View for FontBook {
ui.label("Filter:");
ui.text_edit_singleline(&mut self.filter);
self.filter = self.filter.to_lowercase();
if ui.button("").clicked {
if ui.button("").clicked() {
self.filter.clear();
}
});

View File

@@ -0,0 +1,52 @@
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[derive(Default)]
pub struct InputTest {
info: String,
}
impl super::Demo for InputTest {
fn name(&self) -> &str {
"🖱 Input Test"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
egui::Window::new(self.name())
.open(open)
.resizable(false)
.show(ctx, |ui| {
use super::View;
self.ui(ui);
});
}
}
impl super::View for InputTest {
fn ui(&mut self, ui: &mut egui::Ui) {
let response = ui.add(
egui::Button::new("Click, double-click or drag me with any mouse button")
.sense(egui::Sense::click_and_drag()),
);
let mut new_info = String::new();
for &button in &[
egui::PointerButton::Primary,
egui::PointerButton::Secondary,
egui::PointerButton::Middle,
] {
if response.clicked_by(button) {
new_info += &format!("Clicked by {:?}\n", button);
}
if response.double_clicked_by(button) {
new_info += &format!("Double-clicked by {:?}\n", button);
}
if response.dragged() && ui.input().pointer.button_down(button) {
new_info += &format!("Dragged by {:?}\n", button);
}
}
if !new_info.is_empty() {
self.info = new_info;
}
ui.label(&self.info);
}
}

View File

@@ -12,6 +12,7 @@ mod drag_and_drop;
mod font_book;
pub mod font_contents_emoji;
pub mod font_contents_ubuntu;
pub mod input_test;
mod painting;
mod scrolls;
mod sliders;

View File

@@ -21,7 +21,7 @@ impl Painting {
ui.horizontal(|ui| {
egui::stroke_ui(ui, &mut self.stroke, "Stroke");
ui.separator();
if ui.button("Clear Painting").clicked {
if ui.button("Clear Painting").clicked() {
self.lines.clear();
}
});
@@ -38,12 +38,10 @@ impl Painting {
let current_line = self.lines.last_mut().unwrap();
if response.active {
if let Some(mouse_pos) = ui.input().mouse.pos {
let canvas_pos = mouse_pos - rect.min;
if current_line.last() != Some(&canvas_pos) {
current_line.push(canvas_pos);
}
if let Some(pointer_pos) = response.interact_pointer_pos() {
let canvas_pos = pointer_pos - rect.min;
if current_line.last() != Some(&canvas_pos) {
current_line.push(canvas_pos);
}
} else if !current_line.is_empty() {
self.lines.push(vec![]);

View File

@@ -33,13 +33,13 @@ impl Scrolls {
ui.add(Slider::usize(&mut self.track_item, 1..=50).text("Track Item"));
});
let (scroll_offset, _) = ui.horizontal(|ui| {
let scroll_offset = ui.small_button("Scroll Offset").clicked;
let scroll_offset = ui.small_button("Scroll Offset").clicked();
ui.add(DragValue::f32(&mut self.offset).speed(1.0).suffix("px"));
scroll_offset
});
let scroll_top = ui.button("Scroll to top").clicked;
let scroll_bottom = ui.button("Scroll to bottom").clicked;
let scroll_top = ui.button("Scroll to top").clicked();
let scroll_bottom = ui.button("Scroll to bottom").clicked();
if scroll_bottom || scroll_top {
self.tracking = false;
}

View File

@@ -73,7 +73,7 @@ impl Sliders {
You can always see the full precision value by hovering the value.",
);
if ui.button("Assign PI").clicked {
if ui.button("Assign PI").clicked() {
self.value = std::f64::consts::PI;
}
}

View File

@@ -27,7 +27,7 @@ pub fn toggle(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
let (rect, response) = ui.allocate_exact_size(desired_size, egui::Sense::click());
// 3. Interact: Time to check for clicks!.
if response.clicked {
if response.clicked() {
*on = !*on;
}
@@ -63,7 +63,7 @@ pub fn toggle(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
fn toggle_compact(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
let desired_size = ui.style().spacing.interact_size;
let (rect, response) = ui.allocate_exact_size(desired_size, egui::Sense::click());
*on ^= response.clicked; // toggle if clicked
*on ^= response.clicked(); // toggle if clicked
let how_on = ui.ctx().animate_bool(response.id, *on);
let visuals = ui.style().interact(&response);

View File

@@ -115,7 +115,7 @@ impl super::View for WidgetGallery {
ui.end_row();
ui.label("Button:");
if ui.button("Toggle boolean").clicked {
if ui.button("Toggle boolean").clicked() {
*boolean = !*boolean;
}
ui.end_row();
@@ -123,7 +123,7 @@ impl super::View for WidgetGallery {
ui.label("ImageButton:");
if ui
.add(egui::ImageButton::new(egui::TextureId::Egui, [24.0, 16.0]))
.clicked
.clicked()
{
*boolean = !*boolean;
}

View File

@@ -97,7 +97,7 @@ impl Widgets {
if ui
.add(Button::new("Click me").enabled(self.button_enabled))
.on_hover_text("This will just increase a counter.")
.clicked
.clicked()
{
self.count += 1;
}
@@ -146,7 +146,7 @@ impl Widgets {
ui.horizontal(|ui| {
ui.label("Single line text input:");
let response = ui.text_edit_singleline(&mut self.single_line_text_input);
if response.lost_kb_focus {
if response.lost_kb_focus() {
// The user pressed enter.
}
});

View File

@@ -113,8 +113,8 @@ fn ui_url(ui: &mut egui::Ui, frame: &mut epi::Frame<'_>, url: &mut String) -> Op
ui.horizontal(|ui| {
ui.label("URL:");
trigger_fetch |= ui.text_edit_singleline(url).lost_kb_focus;
trigger_fetch |= ui.button("GET").clicked;
trigger_fetch |= ui.text_edit_singleline(url).lost_kb_focus();
trigger_fetch |= ui.button("GET").clicked();
});
if frame.is_web() {
@@ -122,14 +122,14 @@ fn ui_url(ui: &mut egui::Ui, frame: &mut epi::Frame<'_>, url: &mut String) -> Op
}
ui.horizontal(|ui| {
if ui.button("Source code for this example").clicked {
if ui.button("Source code for this example").clicked() {
*url = format!(
"https://raw.githubusercontent.com/emilk/egui/master/{}",
file!()
);
trigger_fetch = true;
}
if ui.button("Random image").clicked {
if ui.button("Random image").clicked() {
let seed = ui.input().time;
let width = 640;
let height = 480;
@@ -170,7 +170,7 @@ fn ui_resouce(
if let Some(text) = &response.text {
let tooltip = "Click to copy the response body";
if ui.button("📋").on_hover_text(tooltip).clicked {
if ui.button("📋").on_hover_text(tooltip).clicked() {
ui.output().copied_text = text.clone();
}
}

View File

@@ -79,9 +79,9 @@ impl FrameHistory {
let rect = rect.shrink(4.0);
let line_stroke = Stroke::new(1.0, Color32::from_additive_luminance(128));
if let Some(mouse_pos) = ui.input().mouse.pos {
if rect.contains(mouse_pos) {
let y = mouse_pos.y;
if let Some(pointer_pos) = ui.input().pointer.tooltip_pos() {
if rect.contains(pointer_pos) {
let y = pointer_pos.y;
shapes.push(Shape::line_segment(
[pos2(rect.left(), y), pos2(rect.right(), y)],
line_stroke,

View File

@@ -73,7 +73,7 @@ impl epi::App for WrapApp {
for (anchor, app) in self.apps.iter_mut() {
if ui
.selectable_label(self.selected_anchor == anchor, app.name())
.clicked
.clicked()
{
self.selected_anchor = anchor.to_owned();
if frame.is_web() {
@@ -86,7 +86,7 @@ impl epi::App for WrapApp {
if false {
// TODO: fix the overlap on small screens
if let Some(seconds_since_midnight) = frame.info().seconds_since_midnight {
if clock_button(ui, seconds_since_midnight).clicked {
if clock_button(ui, seconds_since_midnight).clicked() {
self.selected_anchor = "clock".to_owned();
if frame.is_web() {
ui.output().open_url = Some("#clock".to_owned());
@@ -234,11 +234,11 @@ impl BackendPanel {
if ui
.button("📱 Phone Size")
.on_hover_text("Resize the window to be small like a phone.")
.clicked
.clicked()
{
frame.set_window_size(egui::Vec2::new(375.0, 812.0)); // iPhone 12 mini
}
if ui.button("Quit").clicked {
if ui.button("Quit").clicked() {
frame.quit();
}
}
@@ -275,7 +275,7 @@ impl BackendPanel {
"Reset scale to native value ({:.1})",
native_pixels_per_point
))
.clicked
.clicked()
{
*pixels_per_point = native_pixels_per_point;
}
@@ -283,7 +283,7 @@ impl BackendPanel {
});
// We wait until mouse release to activate:
if ui.ctx().is_using_mouse() {
if ui.ctx().is_using_pointer() {
None
} else {
Some(*pixels_per_point)