11use egui:: {
22 emath:: { RectTransform , Rot2 } ,
3- vec2, Color32 , Frame , Pos2 , Rect , Sense , Stroke ,
3+ vec2, Color32 , Frame , Pos2 , Rect , Sense , Stroke , Vec2 ,
44} ;
55
66pub struct ZoomRotate {
7+ previous_arrow_start_offset : Vec2 ,
78 rotation : f32 ,
9+ smoothed_velocity : Vec2 ,
10+ translation : Vec2 ,
811 zoom : f32 ,
912}
1013
1114impl Default for ZoomRotate {
1215 fn default ( ) -> Self {
1316 Self {
17+ previous_arrow_start_offset : Vec2 :: ZERO ,
1418 rotation : 0. ,
19+ smoothed_velocity : Vec2 :: ZERO ,
20+ translation : Vec2 :: ZERO ,
1521 zoom : 1. ,
1622 }
1723 }
@@ -64,6 +70,7 @@ impl super::View for ZoomRotate {
6470 Rect :: from_min_size ( Pos2 :: ZERO - painter_proportions, 2. * painter_proportions) ,
6571 response. rect ,
6672 ) ;
73+ let dt = ui. input ( ) . unstable_dt ;
6774
6875 // check for touch input (or the lack thereof) and update zoom and scale factors, plus
6976 // color and width:
@@ -74,6 +81,9 @@ impl super::View for ZoomRotate {
7481 // change (for the current frame) of the touch gesture:
7582 self . zoom *= multi_touch. zoom_delta ;
7683 self . rotation += multi_touch. rotation_delta ;
84+ // the translation we get from `multi_touch` needs to be scaled down to the
85+ // normalized coordinates we use as the basis for painting:
86+ self . translation += to_screen. inverse ( ) . scale ( ) * multi_touch. translation_delta ;
7787 // touch pressure shall make the arrow thicker (not all touch devices support this):
7888 stroke_width += 10. * multi_touch. force ;
7989 // the drawing color depends on the number of touches:
@@ -83,32 +93,48 @@ impl super::View for ZoomRotate {
8393 4 => Color32 :: YELLOW ,
8494 _ => Color32 :: RED ,
8595 } ;
86- // for a smooth touch experience (not strictly required, but I had the impression
87- // that it helps to reduce some lag, especially for the initial touch):
88- ui. ctx ( ) . request_repaint ( ) ;
8996 } else {
9097 // This has nothing to do with the touch gesture. It just smoothly brings the
9198 // painted arrow back into its original position, for a nice visual effect:
92- let dt = ui. input ( ) . unstable_dt ;
9399 const ZOOM_ROTATE_HALF_LIFE : f32 = 1. ; // time[sec] after which half the amount of zoom/rotation will be reverted
94100 let half_life_factor = ( -( 2_f32 . ln ( ) ) / ZOOM_ROTATE_HALF_LIFE * dt) . exp ( ) ;
95101 self . zoom = 1. + ( ( self . zoom - 1. ) * half_life_factor) ;
96102 self . rotation *= half_life_factor;
97- // this is an animation, so we want real-time UI updates:
98- ui. ctx ( ) . request_repaint ( ) ;
103+ self . translation *= half_life_factor;
99104 }
100-
101105 let zoom_and_rotate = self . zoom * Rot2 :: from_angle ( self . rotation ) ;
106+ let arrow_start_offset = self . translation + zoom_and_rotate * vec2 ( -0.5 , 0.5 ) ;
107+ let current_velocity = ( arrow_start_offset - self . previous_arrow_start_offset ) / dt;
108+ self . previous_arrow_start_offset = arrow_start_offset;
109+
110+ // aggregate the average velocity of the arrow's start position from latest samples:
111+ const NUM_SMOOTHING_SAMPLES : f32 = 10. ;
112+ self . smoothed_velocity = ( ( NUM_SMOOTHING_SAMPLES - 1. ) * self . smoothed_velocity
113+ + current_velocity)
114+ / NUM_SMOOTHING_SAMPLES ;
102115
103- // Paints an arrow pointing from bottom-left (-0.5, 0.5) to top-right (0.5, -0.5),
104- // but scaled and rotated according to the current translation :
105- let arrow_start = zoom_and_rotate * vec2 ( - 0.5 , 0.5 ) ;
116+ // Paints an arrow pointing from bottom-left (-0.5, 0.5) to top-right (0.5, -0.5), but
117+ // scaled, rotated, and translated according to the current touch gesture :
118+ let arrow_start = Pos2 :: ZERO + arrow_start_offset ;
106119 let arrow_direction = zoom_and_rotate * vec2 ( 1. , -1. ) ;
107120 painter. arrow (
108- to_screen * ( Pos2 :: ZERO + arrow_start) ,
121+ to_screen * arrow_start,
109122 to_screen. scale ( ) * arrow_direction,
110123 Stroke :: new ( stroke_width, color) ,
111124 ) ;
125+ // Paints a circle at the origin of the arrow. The size and opacity of the circle
126+ // depend on the current velocity, and the circle is translated in the opposite
127+ // direction of the movement, so it follows the origin's movement. Constant factors
128+ // have been determined by trial and error.
129+ let speed = self . smoothed_velocity . length ( ) ;
130+ painter. circle_filled (
131+ to_screen * ( arrow_start - 0.2 * self . smoothed_velocity ) ,
132+ 2. + to_screen. scale ( ) . length ( ) * 0.1 * speed,
133+ Color32 :: RED . linear_multiply ( 1. / ( 1. + ( 5. * speed) . powi ( 2 ) ) ) ,
134+ ) ;
135+
136+ // we want continuous UI updates, so the circle can smoothly follow the arrow's origin:
137+ ui. ctx ( ) . request_repaint ( ) ;
112138 } ) ;
113139 }
114140}
0 commit comments