sdl2 & gamnit: add mouse wheel events
[nit.git] / lib / gamnit / android_two_fingers_motion.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Two fingers camera manipulation, scroll and pinch to zoom
16 #
17 # Provides the main service `EulerCamera::accept_two_fingers_motion`.
18 module android_two_fingers_motion
19
20 # TODO add an abstraction when another platform supports it
21
22 import gamnit
23 import cameras
24 import android
25
26 redef class EulerCamera
27 # Smoothened history of pointers in the current motion event
28 private var last_motion_pointers = new HashMap[Int, Point[Float]] is lazy
29
30 # Start time of the current motion event
31 private var last_motion_start: Int = -1
32
33 # Move and zoom (set `position`) from a two finger pinch and slide option
34 #
35 # Returns `true` if the event is intercepted.
36 #
37 # Should be called from `App::accept_event` before accepting pointer events:
38 #
39 # ~~~nitish
40 # redef class App
41 # redef fun accept_event(event)
42 # do
43 # if world_camera.accept_two_fingers_motion(event) then return true
44 #
45 # # Handle other events...
46 # end
47 # end
48 # ~~~
49 fun accept_two_fingers_motion(event: InputEvent): Bool
50 do
51 if not event isa AndroidMotionEvent then return false
52
53 if event.pointers.length < 2 then
54 # Intercept leftovers of the last motion
55 return event.down_time == last_motion_start
56 end
57
58 # Collect active pointer and their world position
59 var new_motion_pointers = new HashMap[Int, Point[Float]]
60 var ids = new Array[Int]
61 for pointer in event.pointers do
62 var id = pointer.pointer_id
63 ids.add id
64 new_motion_pointers[id] = camera_to_world(pointer.x, pointer.y)
65 end
66
67 var last_motion_pointers = last_motion_pointers
68 if last_motion_start == event.down_time and
69 last_motion_pointers.keys.has(ids[0]) and last_motion_pointers.keys.has(ids[1]) then
70 # Continued motion event
71
72 # Get new and old position for 2 fingers
73 var new_motion_a = new_motion_pointers[ids[0]]
74 var new_motion_b = new_motion_pointers[ids[1]]
75 var prev_pos_a = last_motion_pointers[ids[0]]
76 var prev_pos_b = last_motion_pointers[ids[1]]
77
78 # Move camera
79 var prev_middle_pos = prev_pos_a.lerp(prev_pos_b, 0.5)
80 var new_middle_pos = new_motion_a.lerp(new_motion_b, 0.5)
81 position.x -= new_middle_pos.x - prev_middle_pos.x
82 position.y -= new_middle_pos.y - prev_middle_pos.y
83
84 # Zoom camera
85 var prev_dist = prev_pos_a.dist(prev_pos_b)
86 var new_dist = new_motion_a.dist(new_motion_b)
87
88 position.z = prev_dist * position.z / new_dist
89 else
90 # Prepare for a new motion event
91 last_motion_pointers.clear
92 last_motion_start = event.down_time
93 end
94
95 # Keep a smooth history
96 for i in [0..1] do
97 if last_motion_pointers.keys.has(ids[i]) then
98 last_motion_pointers[ids[i]] = last_motion_pointers[ids[i]]*0.5 +
99 new_motion_pointers[ids[i]]*0.5
100 else last_motion_pointers[ids[i]] = new_motion_pointers[ids[i]]
101 end
102
103 return true
104 end
105 end
106
107 redef class Point[N]
108 private fun *(scalar: Numeric): Point[N]
109 do return new Point[N](x.mul(scalar), y.mul(scalar))
110
111 private fun +(other: Point[N]): Point[N]
112 do return new Point[N](x.add(other.x), y.add(other.y))
113 end