Make multi-touch demo a bit nicer looking
This commit is contained in:
parent
96be848e42
commit
ebd2c859ac
2 changed files with 66 additions and 45 deletions
|
@ -997,6 +997,8 @@ impl Ui {
|
||||||
self.placer.advance_cursor(amount);
|
self.placer.advance_cursor(amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Show some text.
|
||||||
|
///
|
||||||
/// Shortcut for `add(Label::new(text))`
|
/// Shortcut for `add(Label::new(text))`
|
||||||
///
|
///
|
||||||
/// See also [`Label`].
|
/// See also [`Label`].
|
||||||
|
@ -1005,6 +1007,8 @@ impl Ui {
|
||||||
label.into().ui(self)
|
label.into().ui(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Show colored text.
|
||||||
|
///
|
||||||
/// Shortcut for `add(Label::new(text).text_color(color))`
|
/// Shortcut for `add(Label::new(text).text_color(color))`
|
||||||
pub fn colored_label(
|
pub fn colored_label(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -1014,11 +1018,15 @@ impl Ui {
|
||||||
label.into().text_color(color).ui(self)
|
label.into().text_color(color).ui(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Show large text.
|
||||||
|
///
|
||||||
/// Shortcut for `add(Label::new(text).heading())`
|
/// Shortcut for `add(Label::new(text).heading())`
|
||||||
pub fn heading(&mut self, label: impl Into<Label>) -> Response {
|
pub fn heading(&mut self, label: impl Into<Label>) -> Response {
|
||||||
label.into().heading().ui(self)
|
label.into().heading().ui(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Show monospace (fixed width) text.
|
||||||
|
///
|
||||||
/// Shortcut for `add(Label::new(text).monospace())`
|
/// Shortcut for `add(Label::new(text).monospace())`
|
||||||
pub fn monospace(&mut self, label: impl Into<Label>) -> Response {
|
pub fn monospace(&mut self, label: impl Into<Label>) -> Response {
|
||||||
label.into().monospace().ui(self)
|
label.into().monospace().ui(self)
|
||||||
|
@ -1031,11 +1039,20 @@ impl Ui {
|
||||||
label.into().code().ui(self)
|
label.into().code().ui(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Show small text.
|
||||||
|
///
|
||||||
/// Shortcut for `add(Label::new(text).small())`
|
/// Shortcut for `add(Label::new(text).small())`
|
||||||
pub fn small(&mut self, label: impl Into<Label>) -> Response {
|
pub fn small(&mut self, label: impl Into<Label>) -> Response {
|
||||||
label.into().small().ui(self)
|
label.into().small().ui(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Show text that stand out a bit (e.g. slightly brighter).
|
||||||
|
///
|
||||||
|
/// Shortcut for `add(Label::new(text).strong())`
|
||||||
|
pub fn strong(&mut self, label: impl Into<Label>) -> Response {
|
||||||
|
label.into().strong().ui(self)
|
||||||
|
}
|
||||||
|
|
||||||
/// Shortcut for `add(Hyperlink::new(url))`
|
/// Shortcut for `add(Hyperlink::new(url))`
|
||||||
///
|
///
|
||||||
/// See also [`Hyperlink`].
|
/// See also [`Hyperlink`].
|
||||||
|
|
|
@ -4,21 +4,19 @@ use egui::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct MultiTouch {
|
pub struct MultiTouch {
|
||||||
previous_arrow_start_offset: Vec2,
|
|
||||||
rotation: f32,
|
rotation: f32,
|
||||||
smoothed_velocity: Vec2,
|
|
||||||
translation: Vec2,
|
translation: Vec2,
|
||||||
zoom: f32,
|
zoom: f32,
|
||||||
|
last_touch_time: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MultiTouch {
|
impl Default for MultiTouch {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
previous_arrow_start_offset: Vec2::ZERO,
|
|
||||||
rotation: 0.,
|
rotation: 0.,
|
||||||
smoothed_velocity: Vec2::ZERO,
|
|
||||||
translation: Vec2::ZERO,
|
translation: Vec2::ZERO,
|
||||||
zoom: 1.,
|
zoom: 1.,
|
||||||
|
last_touch_time: 0.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,12 +43,19 @@ impl super::View for MultiTouch {
|
||||||
ui.vertical_centered(|ui| {
|
ui.vertical_centered(|ui| {
|
||||||
ui.add(crate::__egui_github_link_file!());
|
ui.add(crate::__egui_github_link_file!());
|
||||||
});
|
});
|
||||||
ui.colored_label(
|
ui.strong(
|
||||||
Color32::RED,
|
"This demo only works on devices with multitouch support (e.g. mobiles and tablets).",
|
||||||
"This only works on devices which send native touch events (mostly mobiles).",
|
|
||||||
);
|
);
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.label("Try touch gestures Pinch/Stretch, Rotation, and Pressure with 2+ fingers.");
|
ui.label("Try touch gestures Pinch/Stretch, Rotation, and Pressure with 2+ fingers.");
|
||||||
|
|
||||||
|
let num_touches = ui
|
||||||
|
.input()
|
||||||
|
.multi_touch()
|
||||||
|
.map(|mt| mt.num_touches)
|
||||||
|
.unwrap_or(0);
|
||||||
|
ui.label(format!("Current touches: {}", num_touches));
|
||||||
|
|
||||||
Frame::dark_canvas(ui.style()).show(ui, |ui| {
|
Frame::dark_canvas(ui.style()).show(ui, |ui| {
|
||||||
// Note that we use `Sense::drag()` although we do not use any pointer events. With
|
// 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
|
// the current implementation, the fact that a touch event of two or more fingers is
|
||||||
|
@ -64,18 +69,18 @@ impl super::View for MultiTouch {
|
||||||
// set up the drawing canvas with normalized coordinates:
|
// set up the drawing canvas with normalized coordinates:
|
||||||
let (response, painter) =
|
let (response, painter) =
|
||||||
ui.allocate_painter(ui.available_size_before_wrap(), Sense::drag());
|
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:
|
// normalize painter coordinates to ±1 units in each direction with [0,0] in the center:
|
||||||
let painter_proportions = response.rect.square_proportions();
|
let painter_proportions = response.rect.square_proportions();
|
||||||
let to_screen = RectTransform::from_to(
|
let to_screen = RectTransform::from_to(
|
||||||
Rect::from_min_size(Pos2::ZERO - painter_proportions, 2. * painter_proportions),
|
Rect::from_min_size(Pos2::ZERO - painter_proportions, 2. * painter_proportions),
|
||||||
response.rect,
|
response.rect,
|
||||||
);
|
);
|
||||||
let dt = ui.input().unstable_dt;
|
|
||||||
|
|
||||||
// check for touch input (or the lack thereof) and update zoom and scale factors, plus
|
// check for touch input (or the lack thereof) and update zoom and scale factors, plus
|
||||||
// color and width:
|
// color and width:
|
||||||
let mut stroke_width = 1.;
|
let mut stroke_width = 1.;
|
||||||
let mut color = Color32::GRAY;
|
let color = Color32::GRAY;
|
||||||
if let Some(multi_touch) = ui.input().multi_touch() {
|
if let Some(multi_touch) = ui.input().multi_touch() {
|
||||||
// This adjusts the current zoom factor and rotation angle according to the dynamic
|
// This adjusts the current zoom factor and rotation angle according to the dynamic
|
||||||
// change (for the current frame) of the touch gesture:
|
// change (for the current frame) of the touch gesture:
|
||||||
|
@ -84,34 +89,15 @@ impl super::View for MultiTouch {
|
||||||
// the translation we get from `multi_touch` needs to be scaled down to the
|
// the translation we get from `multi_touch` needs to be scaled down to the
|
||||||
// normalized coordinates we use as the basis for painting:
|
// normalized coordinates we use as the basis for painting:
|
||||||
self.translation += to_screen.inverse().scale() * multi_touch.translation_delta;
|
self.translation += to_screen.inverse().scale() * multi_touch.translation_delta;
|
||||||
// touch pressure shall make the arrow thicker (not all touch devices support this):
|
// touch pressure will make the arrow thicker (not all touch devices support this):
|
||||||
stroke_width += 10. * multi_touch.force;
|
stroke_width += 10. * multi_touch.force;
|
||||||
// the drawing color depends on the number of touches:
|
|
||||||
color = match multi_touch.num_touches {
|
self.last_touch_time = ui.input().time;
|
||||||
2 => Color32::GREEN,
|
|
||||||
3 => Color32::BLUE,
|
|
||||||
4 => Color32::YELLOW,
|
|
||||||
_ => Color32::RED,
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
// This has nothing to do with the touch gesture. It just smoothly brings the
|
self.slowly_reset(ui);
|
||||||
// painted arrow back into its original position, for a nice visual effect:
|
|
||||||
const ZOOM_ROTATE_HALF_LIFE: f32 = 1.; // time[sec] after which half the amount of zoom/rotation will be reverted
|
|
||||||
let half_life_factor = (-(2_f32.ln()) / ZOOM_ROTATE_HALF_LIFE * dt).exp();
|
|
||||||
self.zoom = 1. + ((self.zoom - 1.) * half_life_factor);
|
|
||||||
self.rotation *= half_life_factor;
|
|
||||||
self.translation *= half_life_factor;
|
|
||||||
}
|
}
|
||||||
let zoom_and_rotate = self.zoom * Rot2::from_angle(self.rotation);
|
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);
|
let arrow_start_offset = self.translation + zoom_and_rotate * vec2(-0.5, 0.5);
|
||||||
let current_velocity = (arrow_start_offset - self.previous_arrow_start_offset) / dt;
|
|
||||||
self.previous_arrow_start_offset = arrow_start_offset;
|
|
||||||
|
|
||||||
// aggregate the average velocity of the arrow's start position from latest samples:
|
|
||||||
const NUM_SMOOTHING_SAMPLES: f32 = 10.;
|
|
||||||
self.smoothed_velocity = ((NUM_SMOOTHING_SAMPLES - 1.) * self.smoothed_velocity
|
|
||||||
+ current_velocity)
|
|
||||||
/ NUM_SMOOTHING_SAMPLES;
|
|
||||||
|
|
||||||
// Paints an arrow pointing from bottom-left (-0.5, 0.5) to top-right (0.5, -0.5), but
|
// 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:
|
// scaled, rotated, and translated according to the current touch gesture:
|
||||||
|
@ -122,19 +108,37 @@ impl super::View for MultiTouch {
|
||||||
to_screen.scale() * arrow_direction,
|
to_screen.scale() * arrow_direction,
|
||||||
Stroke::new(stroke_width, color),
|
Stroke::new(stroke_width, color),
|
||||||
);
|
);
|
||||||
// Paints a circle at the origin of the arrow. The size and opacity of the circle
|
|
||||||
// depend on the current velocity, and the circle is translated in the opposite
|
|
||||||
// direction of the movement, so it follows the origin's movement. Constant factors
|
|
||||||
// have been determined by trial and error.
|
|
||||||
let speed = self.smoothed_velocity.length();
|
|
||||||
painter.circle_filled(
|
|
||||||
to_screen * (arrow_start - 0.2 * self.smoothed_velocity),
|
|
||||||
2. + to_screen.scale().length() * 0.1 * speed,
|
|
||||||
Color32::RED.linear_multiply(1. / (1. + (5. * speed).powi(2))),
|
|
||||||
);
|
|
||||||
|
|
||||||
// we want continuous UI updates, so the circle can smoothly follow the arrow's origin:
|
|
||||||
ui.ctx().request_repaint();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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().time - self.last_touch_time) as f32;
|
||||||
|
|
||||||
|
let delay = 0.5;
|
||||||
|
if time_since_last_touch < delay {
|
||||||
|
ui.ctx().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().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.ctx().request_repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue