mirror of
https://github.com/koreader/koreader.git
synced 2025-08-10 00:52:38 +00:00
add pinch, spread, rotate and inward/outward pan gestures
This commit is contained in:
@@ -48,7 +48,12 @@ Current detectable gestures:
|
||||
* pan
|
||||
* hold
|
||||
* swipe
|
||||
* pinch
|
||||
* spread
|
||||
* rotate
|
||||
* double_tap
|
||||
* inward_pan
|
||||
* outward_pan
|
||||
* pan_release
|
||||
* two_finger_tap
|
||||
* two_finger_pan
|
||||
@@ -83,7 +88,17 @@ GestureDetector = {
|
||||
DOUBLE_TAP_DISTANCE = 50,
|
||||
TWO_FINGER_TAP_REGION = 20,
|
||||
PAN_THRESHOLD = 50,
|
||||
|
||||
-- pinch/spread direction table
|
||||
DIRECTION_TABLE = {
|
||||
east = "horizontal",
|
||||
west = "horizontal",
|
||||
north = "vertical",
|
||||
south = "vertical",
|
||||
northeast = "diagonal",
|
||||
northwest = "diagonal",
|
||||
southeast = "diagonal",
|
||||
southwest = "diagonal",
|
||||
},
|
||||
-- states are stored in separated slots
|
||||
states = {},
|
||||
track_ids = {},
|
||||
@@ -164,23 +179,29 @@ end
|
||||
compare last_pan with first_tev in this slot
|
||||
return pan direction and distance
|
||||
--]]
|
||||
function GestureDetector:getPath(tev)
|
||||
local slot = tev.slot
|
||||
function GestureDetector:getPath(slot)
|
||||
local x_diff = self.last_tevs[slot].x - self.first_tevs[slot].x
|
||||
local y_diff = self.last_tevs[slot].y - self.first_tevs[slot].y
|
||||
local direction = nil
|
||||
local distance = math.sqrt(x_diff*x_diff + y_diff*y_diff)
|
||||
if x_diff == 0 and y_diff == 0 then
|
||||
elseif (math.abs(x_diff) > math.abs(y_diff)) then
|
||||
direction = x_diff < 0 and "left" or "right"
|
||||
else
|
||||
direction = y_diff < 0 and "up" or "down"
|
||||
local v_direction = y_diff < 0 and "north" or "south"
|
||||
local h_direction = x_diff < 0 and "west" or "east"
|
||||
if math.abs(y_diff) > 0.577*math.abs(x_diff)
|
||||
and math.abs(y_diff) < 1.732*math.abs(x_diff) then
|
||||
direction = v_direction..h_direction
|
||||
elseif (math.abs(x_diff) > math.abs(y_diff)) then
|
||||
direction = h_direction
|
||||
else
|
||||
direction = v_direction
|
||||
end
|
||||
end
|
||||
return direction, distance
|
||||
end
|
||||
|
||||
function GestureDetector:isSwipe(tev)
|
||||
local slot = tev.slot
|
||||
function GestureDetector:isSwipe(slot)
|
||||
if not self.first_tevs[slot] or not self.last_tevs[slot] then return end
|
||||
local tv_diff = self.first_tevs[slot].timev - self.last_tevs[slot].timev
|
||||
if (tv_diff.sec == 0) and (tv_diff.usec < self.SWIPE_INTERVAL) then
|
||||
local x_diff = self.last_tevs[slot].x - self.first_tevs[slot].x
|
||||
@@ -191,6 +212,13 @@ function GestureDetector:isSwipe(tev)
|
||||
end
|
||||
end
|
||||
|
||||
function GestureDetector:getRotate(orig_point, start_point, end_point)
|
||||
local a = orig_point:distance(start_point)
|
||||
local b = orig_point:distance(end_point)
|
||||
local c = start_point:distance(end_point)
|
||||
return math.acos((a*a + b*b - c*c)/(2*a*b))*180/math.pi
|
||||
end
|
||||
|
||||
--[[
|
||||
Warning! this method won't update self.state, you need to do it
|
||||
in each state method!
|
||||
@@ -372,56 +400,61 @@ function GestureDetector:panState(tev)
|
||||
local slot = tev.slot
|
||||
if tev.id == -1 then
|
||||
-- end of pan, signal swipe gesture if necessary
|
||||
if self:isSwipe(tev) then
|
||||
local swipe_direction, swipe_distance = self:getPath(tev)
|
||||
local start_pos = Geom:new{
|
||||
x = self.first_tevs[slot].x,
|
||||
y = self.first_tevs[slot].y,
|
||||
w = 0, h = 0,
|
||||
}
|
||||
local swipe_ev = {
|
||||
ges = "swipe",
|
||||
-- use first pan tev coordination as swipe start point
|
||||
pos = start_pos,
|
||||
direction = swipe_direction,
|
||||
distance = swipe_distance,
|
||||
time = tev.timev,
|
||||
}
|
||||
if self:isSwipe(slot) then
|
||||
if self.detectings[0] and self.detectings[1] then
|
||||
DEBUG("two finger swipe", swipe_direction, swipe_distance, "detected")
|
||||
swipe_ev.ges = "two_finger_swipe"
|
||||
local ges_ev = self:handleTwoFingerPan(tev)
|
||||
self:clearStates()
|
||||
if ges_ev then
|
||||
if ges_ev.ges == "two_finger_pan" then
|
||||
ges_ev.ges = "two_finger_swipe"
|
||||
elseif ges_ev.ges == "inward_pan" then
|
||||
ges_ev.ges = "pinch"
|
||||
elseif ges_ev.ges == "outward_pan" then
|
||||
ges_ev.ges = "spread"
|
||||
end
|
||||
end
|
||||
DEBUG(ges_ev.ges, ges_ev.direction, ges_ev.distance, "detected")
|
||||
return ges_ev
|
||||
else
|
||||
DEBUG("swipe", swipe_direction, swipe_distance, "detected in slot", slot)
|
||||
self:clearState(slot)
|
||||
return self:handleSwipe(tev)
|
||||
end
|
||||
return swipe_ev
|
||||
else -- if end of pan is not swipe then it must be pan release.
|
||||
local release_pos = Geom:new{
|
||||
x = self.last_tevs[slot].x,
|
||||
y = self.last_tevs[slot].y,
|
||||
w = 0, h = 0,
|
||||
}
|
||||
local pan_ev = {
|
||||
ges = "pan_release",
|
||||
pos = release_pos,
|
||||
time = tev.timev,
|
||||
}
|
||||
if self.detectings[0] and self.detectings[1] then
|
||||
DEBUG("two finger pan release detected")
|
||||
pan_ev.ges = "two_finger_pan_release"
|
||||
self:clearStates()
|
||||
else
|
||||
DEBUG("pan release detected in slot", slot)
|
||||
self:clearState(slot)
|
||||
end
|
||||
return pan_ev
|
||||
return self:handlePanRelease(tev)
|
||||
end
|
||||
else
|
||||
if self.states[slot] ~= self.panState then
|
||||
self.states[slot] = self.panState
|
||||
end
|
||||
local pan_direction, pan_distance = self:getPath(tev)
|
||||
return self:handlePan(tev)
|
||||
end
|
||||
end
|
||||
|
||||
function GestureDetector:handleSwipe(tev)
|
||||
local slot = tev.slot
|
||||
local swipe_direction, swipe_distance = self:getPath(slot)
|
||||
local start_pos = Geom:new{
|
||||
x = self.first_tevs[slot].x,
|
||||
y = self.first_tevs[slot].y,
|
||||
w = 0, h = 0,
|
||||
}
|
||||
DEBUG("swipe", swipe_direction, swipe_distance, "detected in slot", slot)
|
||||
self:clearState(slot)
|
||||
return {
|
||||
ges = "swipe",
|
||||
-- use first pan tev coordination as swipe start point
|
||||
pos = start_pos,
|
||||
direction = swipe_direction,
|
||||
distance = swipe_distance,
|
||||
time = tev.timev,
|
||||
}
|
||||
end
|
||||
|
||||
function GestureDetector:handlePan(tev)
|
||||
local slot = tev.slot
|
||||
if self.detectings[0] and self.detectings[1] then
|
||||
return self:handleTwoFingerPan(tev)
|
||||
else
|
||||
local pan_direction, pan_distance = self:getPath(slot)
|
||||
local pan_ev = {
|
||||
ges = "pan",
|
||||
relative = {
|
||||
@@ -441,14 +474,93 @@ function GestureDetector:panState(tev)
|
||||
y = self.last_tevs[slot].y,
|
||||
w = 0, h = 0,
|
||||
}
|
||||
if self.detectings[0] and self.detectings[1] then
|
||||
pan_ev.ges = "two_finger_pan"
|
||||
DEBUG("two finger pan detected")
|
||||
end
|
||||
return pan_ev
|
||||
end
|
||||
end
|
||||
|
||||
function GestureDetector:handleTwoFingerPan(tev)
|
||||
-- triggering slot
|
||||
local tslot = tev.slot
|
||||
-- reference slot
|
||||
local rslot = tslot and 0 or 1
|
||||
local tpan_dir, tpan_dis = self:getPath(tslot)
|
||||
local tstart_pos = Geom:new{
|
||||
x = self.first_tevs[tslot].x,
|
||||
y = self.first_tevs[tslot].y,
|
||||
w = 0, h = 0,
|
||||
}
|
||||
local tend_pos = Geom:new{
|
||||
x = self.last_tevs[tslot].x,
|
||||
y = self.last_tevs[tslot].y,
|
||||
w = 0, h = 0,
|
||||
}
|
||||
local rstart_pos = Geom:new{
|
||||
x = self.first_tevs[rslot].x,
|
||||
y = self.first_tevs[rslot].y,
|
||||
w = 0, h = 0,
|
||||
}
|
||||
if self.states[rslot] == self.panState then
|
||||
local rpan_dir, rpan_dis = self:getPath(rslot)
|
||||
local rend_pos = Geom:new{
|
||||
x = self.last_tevs[rslot].x,
|
||||
y = self.last_tevs[rslot].y,
|
||||
w = 0, h = 0,
|
||||
}
|
||||
local start_distance = tstart_pos:distance(rstart_pos)
|
||||
local end_distance = tend_pos:distance(rend_pos)
|
||||
local ges_ev = {
|
||||
ges = "two_finger_pan",
|
||||
-- use midpoint of tstart and rstart as swipe start point
|
||||
pos = tstart_pos:midpoint(rstart_pos),
|
||||
distance = tpan_dis + rpan_dis,
|
||||
direction = tpan_dir,
|
||||
time = tev.timev,
|
||||
}
|
||||
if tpan_dir ~= rpan_dir then
|
||||
if start_distance > end_distance then
|
||||
ges_ev.ges = "inward_pan"
|
||||
else
|
||||
ges_ev.ges = "outward_pan"
|
||||
end
|
||||
ges_ev.direction = self.DIRECTION_TABLE[tpan_dir]
|
||||
end
|
||||
DEBUG(ges_ev.ges, ges_ev.direction, ges_ev.distance, "detected")
|
||||
return ges_ev
|
||||
elseif self.states[rslot] == self.holdState then
|
||||
local angle = self:getRotate(rstart_pos, tstart_pos, tend_pos)
|
||||
DEBUG("rotate", angle, "detected")
|
||||
return {
|
||||
ges = "rotate",
|
||||
pos = rstart_pos,
|
||||
angle = angle,
|
||||
time = tev.timev,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
function GestureDetector:handlePanRelease(tev)
|
||||
local slot = tev.slot
|
||||
local release_pos = Geom:new{
|
||||
x = self.last_tevs[slot].x,
|
||||
y = self.last_tevs[slot].y,
|
||||
w = 0, h = 0,
|
||||
}
|
||||
local pan_ev = {
|
||||
ges = "pan_release",
|
||||
pos = release_pos,
|
||||
time = tev.timev,
|
||||
}
|
||||
if self.detectings[0] and self.detectings[1] then
|
||||
DEBUG("two finger pan release detected")
|
||||
pan_ev.ges = "two_finger_pan_release"
|
||||
self:clearStates()
|
||||
else
|
||||
DEBUG("pan release detected in slot", slot)
|
||||
self:clearState(slot)
|
||||
end
|
||||
return pan_ev
|
||||
end
|
||||
|
||||
function GestureDetector:holdState(tev, hold)
|
||||
DEBUG("in hold state...")
|
||||
local slot = tev.slot
|
||||
@@ -495,19 +607,31 @@ function GestureDetector:adjustGesCoordinate(ges)
|
||||
if ges.pos then
|
||||
ges.pos.x, ges.pos.y = (Screen.width - ges.pos.y), (ges.pos.x)
|
||||
end
|
||||
if ges.ges == "swipe" or ges.ges == "pan" then
|
||||
if ges.direction == "down" then
|
||||
ges.direction = "left"
|
||||
elseif ges.direction == "up" then
|
||||
ges.direction = "right"
|
||||
elseif ges.direction == "right" then
|
||||
ges.direction = "down"
|
||||
elseif ges.direction == "left" then
|
||||
ges.direction = "up"
|
||||
if ges.ges == "swipe" or ges.ges == "pan"
|
||||
or ges.ges == "two_finger_swipe"
|
||||
or ges.ges == "two_finger_pan" then
|
||||
if ges.direction == "north" then
|
||||
ges.direction = "east"
|
||||
elseif ges.direction == "south" then
|
||||
ges.direction = "west"
|
||||
elseif ges.direction == "east" then
|
||||
ges.direction = "south"
|
||||
elseif ges.direction == "west" then
|
||||
ges.direction = "north"
|
||||
end
|
||||
elseif ges.ges == "pinch" or ges.ges == "spread"
|
||||
or ges.ges == "inward_pan"
|
||||
or ges.ges == "outward_pan" then
|
||||
if ges.direction == "northeast" then
|
||||
ges.direction = "southeast"
|
||||
elseif ges.direction == "northwest" then
|
||||
ges.direction = "northeast"
|
||||
elseif ges.direction == "southeast" then
|
||||
ges.direction = "southwest"
|
||||
elseif ges.direction == "southwest" then
|
||||
ges.direction = "northwest"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return ges
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user