mirror of
https://github.com/emilk/egui.git
synced 2026-08-30 05:10:03 -04:00
166 lines
6.3 KiB
Rust
166 lines
6.3 KiB
Rust
use egui::{
|
|
Color32, Event, Frame, Pos2, Rect, Sense, Stroke, Vec2,
|
|
emath::{RectTransform, Rot2},
|
|
vec2,
|
|
};
|
|
|
|
pub struct MultiTouch {
|
|
rotation: f32,
|
|
translation: Vec2,
|
|
zoom: f32,
|
|
last_touch_time: f64,
|
|
}
|
|
|
|
impl Default for MultiTouch {
|
|
fn default() -> Self {
|
|
Self {
|
|
rotation: 0.,
|
|
translation: Vec2::ZERO,
|
|
zoom: 1.,
|
|
last_touch_time: 0.0,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl crate::Demo for MultiTouch {
|
|
fn name(&self) -> &'static str {
|
|
"👌 Multi Touch"
|
|
}
|
|
|
|
fn show(&mut self, ui: &mut egui::Ui, open: &mut bool) {
|
|
egui::Window::new(self.name())
|
|
.open(open)
|
|
.default_size(vec2(544.0, 512.0))
|
|
.resizable(true)
|
|
.constrain_to(ui.available_rect_before_wrap())
|
|
.show(ui, |ui| {
|
|
use crate::View as _;
|
|
self.ui(ui);
|
|
});
|
|
}
|
|
}
|
|
|
|
impl crate::View for MultiTouch {
|
|
fn ui(&mut self, ui: &mut egui::Ui) {
|
|
ui.vertical_centered(|ui| {
|
|
ui.add(crate::egui_github_link_file!());
|
|
});
|
|
ui.strong(
|
|
"This demo only works on devices with multitouch support (e.g. mobiles, tablets, and trackpads).",
|
|
);
|
|
ui.separator();
|
|
ui.label("Try touch gestures Pinch/Stretch, Rotation, and Pressure with 2+ fingers.");
|
|
|
|
let relative_pointer_gesture = ui.input(|i| {
|
|
i.events.iter().any(|event| {
|
|
matches!(
|
|
event,
|
|
Event::MouseWheel { .. } | Event::Zoom { .. } | Event::Rotate { .. }
|
|
)
|
|
})
|
|
});
|
|
let num_touches = ui.input(|i| i.multi_touch().map_or(0, |mt| mt.num_touches));
|
|
let num_touches_str = format!("{num_touches}-finger touch");
|
|
ui.label(format!(
|
|
"Input source: {}",
|
|
if ui.input(|i| i.multi_touch().is_some()) {
|
|
num_touches_str.as_str()
|
|
} else if relative_pointer_gesture {
|
|
"cursor"
|
|
} else {
|
|
"none"
|
|
}
|
|
));
|
|
|
|
let color = if ui.visuals().dark_mode {
|
|
Color32::WHITE
|
|
} else {
|
|
Color32::BLACK
|
|
};
|
|
|
|
Frame::canvas(ui.style()).show(ui, |ui| {
|
|
// Note that we use `Sense::drag()` although we do not use any pointer events. With
|
|
// the current implementation, the fact that a touch event of two or more fingers is
|
|
// recognized, does not mean that the pointer events are suppressed, which are always
|
|
// generated for the first finger. Therefore, if we do not explicitly consume pointer
|
|
// events, the window will move around, not only when dragged with a single finger, but
|
|
// also when a two-finger touch is active. I guess this problem can only be cleanly
|
|
// solved when the synthetic pointer events are created by egui, and not by the
|
|
// backend.
|
|
|
|
// set up the drawing canvas with normalized coordinates:
|
|
let (response, painter) =
|
|
ui.allocate_painter(ui.available_size_before_wrap(), Sense::drag());
|
|
|
|
// normalize painter coordinates to ±1 units in each direction with [0,0] in the center:
|
|
let painter_proportions = response.rect.square_proportions();
|
|
let to_screen = RectTransform::from_to(
|
|
Rect::from_min_size(Pos2::ZERO - painter_proportions, 2. * painter_proportions),
|
|
response.rect,
|
|
);
|
|
|
|
// check for touch input (or the lack thereof) and update zoom and scale factors, plus
|
|
// color and width:
|
|
let mut stroke_width = 1.;
|
|
if ui.input(|i| i.multi_touch().is_some()) || relative_pointer_gesture {
|
|
ui.input(|input| {
|
|
// This adjusts the current zoom factor, rotation angle, and translation according
|
|
// to the dynamic change (for the current frame) of the touch gesture:
|
|
self.zoom *= input.zoom_delta();
|
|
self.rotation += input.rotation_delta();
|
|
self.translation += to_screen.inverse().scale() * input.translation_delta();
|
|
// touch pressure will make the arrow thicker (not all touch devices support this):
|
|
stroke_width += 10. * input.multi_touch().map_or(0.0, |touch| touch.force);
|
|
|
|
self.last_touch_time = input.time;
|
|
});
|
|
} else {
|
|
self.slowly_reset(ui);
|
|
}
|
|
let zoom_and_rotate = self.zoom * Rot2::from_angle(self.rotation);
|
|
let arrow_start_offset = self.translation + zoom_and_rotate * vec2(-0.5, 0.5);
|
|
|
|
// Paints an arrow pointing from bottom-left (-0.5, 0.5) to top-right (0.5, -0.5), but
|
|
// scaled, rotated, and translated according to the current touch gesture:
|
|
let arrow_start = Pos2::ZERO + arrow_start_offset;
|
|
let arrow_direction = zoom_and_rotate * vec2(1., -1.);
|
|
painter.arrow(
|
|
to_screen * arrow_start,
|
|
to_screen.scale() * arrow_direction,
|
|
Stroke::new(stroke_width, color),
|
|
);
|
|
});
|
|
}
|
|
}
|
|
|
|
impl MultiTouch {
|
|
fn slowly_reset(&mut self, ui: &egui::Ui) {
|
|
// This has nothing to do with the touch gesture. It just smoothly brings the
|
|
// painted arrow back into its original position, for a nice visual effect:
|
|
|
|
let time_since_last_touch = (ui.input(|i| i.time) - self.last_touch_time) as f32;
|
|
|
|
let delay = 0.5;
|
|
if time_since_last_touch < delay {
|
|
ui.request_repaint();
|
|
} else {
|
|
// seconds after which half the amount of zoom/rotation will be reverted:
|
|
let half_life =
|
|
egui::remap_clamp(time_since_last_touch, delay..=1.0, 1.0..=0.0).powf(4.0);
|
|
|
|
if half_life <= 1e-3 {
|
|
self.zoom = 1.0;
|
|
self.rotation = 0.0;
|
|
self.translation = Vec2::ZERO;
|
|
} else {
|
|
let dt = ui.input(|i| i.unstable_dt);
|
|
let half_life_factor = (-(2_f32.ln()) / half_life * dt).exp();
|
|
self.zoom = 1. + ((self.zoom - 1.) * half_life_factor);
|
|
self.rotation *= half_life_factor;
|
|
self.translation *= half_life_factor;
|
|
ui.request_repaint();
|
|
}
|
|
}
|
|
}
|
|
}
|