Merge pull request #21 from chrox/more_gestures

add pinch, spread, rotate and inward/outward pan gestures
pull/22/head
{Qingping,Dave} Hou 11 years ago
commit 66d7de06ad

@ -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
DEBUG(ges_ev.ges, ges_ev.direction, ges_ev.distance, "detected")
end
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,35 @@ 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"
elseif 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
elseif ges.ges == "pinch" or ges.ges == "spread"
or ges.ges == "inward_pan"
or ges.ges == "outward_pan" then
if ges.direction == "horizontal" then
ges.direction = "vertical"
elseif ges.direction == "vertical" then
ges.direction = "horizontal"
end
end
end
return ges
end

@ -173,13 +173,13 @@ function ReaderPaging:flipping(flipping_page, flipping_ges)
local whole = self.number_of_pages
local rel_proportion = flipping_ges.distance / Screen:getWidth()
local abs_proportion = flipping_ges.distance / Screen:getHeight()
if flipping_ges.direction == "right" then
if flipping_ges.direction == "east" then
self:gotoPage(flipping_page - math.floor(read*rel_proportion))
elseif flipping_ges.direction == "left" then
elseif flipping_ges.direction == "west" then
self:gotoPage(flipping_page + math.floor(unread*rel_proportion))
elseif flipping_ges.direction == "down" then
elseif flipping_ges.direction == "south" then
self:gotoPage(flipping_page - math.floor(whole*abs_proportion))
elseif flipping_ges.direction == "up" then
elseif flipping_ges.direction == "north" then
self:gotoPage(flipping_page + math.floor(whole*abs_proportion))
end
UIManager:setDirty(self.view.dialog, "partial")
@ -189,9 +189,9 @@ function ReaderPaging:onSwipe(arg, ges)
if self.flipping_mode then
self:flipping(self.flipping_page, ges)
self:updateFlippingPage(self.current_page)
elseif ges.direction == "left" or ges.direction == "up" then
elseif ges.direction == "west" or ges.direction == "north" then
self:onPagingRel(1)
elseif ges.direction == "right" or ges.direction == "down" then
elseif ges.direction == "east" or ges.direction == "south" then
self:onPagingRel(-1)
end
return true

@ -1,12 +1,42 @@
ReaderRotation = InputContainer:new{
key_events = {
-- these will all generate the same event, just with different arguments
RotateLeft = { {"J"}, doc = "rotate left by 90 degrees", event = "Rotate", args = -90 },
RotateRight = { {"K"}, doc = "rotate right by 90 degrees", event = "Rotate", args = 90 },
},
ROTATE_ANGLE_THRESHOLD = 15,
current_rotation = 0
}
function ReaderRotation:init()
if Device:hasKeyboard() then
self.key_events = {
-- these will all generate the same event, just with different arguments
RotateLeft = { {"J"}, doc = "rotate left by 90 degrees", event = "Rotate", args = -90 },
RotateRight = { {"K"}, doc = "rotate right by 90 degrees", event = "Rotate", args = 90 },
}
end
if Device:isTouchDevice() then
self.ges_events = {
RotateGes = {
GestureRange:new{
ges = "rotate",
range = Geom:new{
x = 0, y = 0,
w = Screen:getWidth(),
h = Screen:getHeight(),
}
}
},
TwoFingerPanRelease = {
GestureRange:new{
ges = "two_finger_pan_release",
range = Geom:new{
x = 0, y = 0,
w = Screen:getWidth(),
h = Screen:getHeight(),
}
}
}
}
end
end
-- TODO: reset rotation on new document, maybe on new page?
function ReaderRotation:onRotate(rotate_by)
@ -14,3 +44,19 @@ function ReaderRotation:onRotate(rotate_by)
self.ui:handleEvent(Event:new("RotationUpdate", self.current_rotation))
return true
end
function ReaderRotation:onRotateGes(arg, ges)
self.ratate_angle = ges.angle
return true
end
function ReaderRotation:onTwoFingerPanRelease(arg, ges)
if self.ratate_angle and self.ratate_angle > self.ROTATE_ANGLE_THRESHOLD then
if Screen:getScreenMode() == "portrait" then
self.ui:handleEvent(Event:new("SetScreenMode", "landscape"))
else
self.ui:handleEvent(Event:new("SetScreenMode", "portrait"))
end
self.ratate_angle = nil
end
end

@ -52,6 +52,30 @@ function ReaderZooming:init()
},
}
end
if Device:isTouchDevice() then
self.ges_events = {
Spread = {
GestureRange:new{
ges = "spread",
range = Geom:new{
x = 0, y = 0,
w = Screen:getWidth(),
h = Screen:getHeight(),
}
}
},
Pinch = {
GestureRange:new{
ges = "pinch",
range = Geom:new{
x = 0, y = 0,
w = Screen:getWidth(),
h = Screen:getHeight(),
}
}
},
}
end
self.ui.menu:registerToMainMenu(self)
end
@ -70,6 +94,28 @@ function ReaderZooming:onCloseDocument()
self.ui.doc_settings:saveSetting("zoom_mode", self.zoom_mode)
end
function ReaderZooming:onSpread(arg, ges)
if ges.direction == "horizontal" then
self:setZoomMode("contentwidth")
elseif ges.direction == "vertical" then
self:setZoomMode("contentheight")
elseif ges.direction == "diagonal" then
self:setZoomMode("content")
end
return true
end
function ReaderZooming:onPinch(arg, ges)
if ges.direction == "diagonal" then
self:setZoomMode("page")
elseif ges.direction == "horizontal" then
self:setZoomMode("pagewidth")
elseif ges.direction == "vertical" then
self:setZoomMode("pageheight")
end
return true
end
function ReaderZooming:onSetDimensions(dimensions)
-- we were resized
self.dimen = dimensions
@ -174,11 +220,15 @@ end
function ReaderZooming:genSetZoomModeCallBack(mode)
return function()
self.ui:handleEvent(Event:new("SetZoomMode", mode))
self.ui:handleEvent(Event:new("InitScrollPageStates"))
self:setZoomMode(mode)
end
end
function ReaderZooming:setZoomMode(mode)
self.ui:handleEvent(Event:new("SetZoomMode", mode))
self.ui:handleEvent(Event:new("InitScrollPageStates"))
end
function ReaderZooming:addToMainMenu(tab_item_table)
if self.ui.document.info.has_pages then
table.insert(tab_item_table.typeset, {

@ -130,9 +130,9 @@ function BBoxWidget:adjustScreenBBox(ges, relative)
elseif nearest == upper_center then
if relative then
local delta = 0
if ges.direction == "up" then
if ges.direction == "north" then
delta = -ges.distance / 5
elseif ges.direction == "down" then
elseif ges.direction == "south" then
delta = ges.distance / 5
end
upper_left.y = upper_left.y + delta
@ -142,9 +142,9 @@ function BBoxWidget:adjustScreenBBox(ges, relative)
elseif nearest == right_center then
if relative then
local delta = 0
if ges.direction == "left" then
if ges.direction == "west" then
delta = -ges.distance / 5
elseif ges.direction == "right" then
elseif ges.direction == "east" then
delta = ges.distance / 5
end
bottom_right.x = bottom_right.x + delta
@ -154,9 +154,9 @@ function BBoxWidget:adjustScreenBBox(ges, relative)
elseif nearest == bottom_center then
if relative then
local delta = 0
if ges.direction == "up" then
if ges.direction == "north" then
delta = -ges.distance / 5
elseif ges.direction == "down" then
elseif ges.direction == "south" then
delta = ges.distance / 5
end
bottom_right.y = bottom_right.y + delta
@ -166,9 +166,9 @@ function BBoxWidget:adjustScreenBBox(ges, relative)
elseif nearest == left_center then
if relative then
local delta = 0
if ges.direction == "left" then
if ges.direction == "west" then
delta = -ges.distance / 5
elseif ges.direction == "right" then
elseif ges.direction == "east" then
delta = ges.distance / 5
end
upper_left.x = upper_left.x + delta

@ -562,9 +562,9 @@ function Menu:onTapCloseAllMenus(arg, ges_ev)
end
function Menu:onSwipe(arg, ges_ev)
if ges_ev.direction == "left" then
if ges_ev.direction == "west" then
self:onNextPage()
elseif ges_ev.direction == "right" then
elseif ges_ev.direction == "east" then
self:onPrevPage()
end
end

@ -358,9 +358,9 @@ function TouchMenu:onPrevPage()
end
function TouchMenu:onSwipe(arg, ges_ev)
if ges_ev.direction == "left" then
if ges_ev.direction == "west" then
self:onNextPage()
elseif ges_ev.direction == "right" then
elseif ges_ev.direction == "east" then
self:onPrevPage()
end
end

Loading…
Cancel
Save