diff --git a/frontend/ui/gesturedetector.lua b/frontend/ui/gesturedetector.lua index d4faa9a58..68550623d 100644 --- a/frontend/ui/gesturedetector.lua +++ b/frontend/ui/gesturedetector.lua @@ -6,6 +6,8 @@ GestureRange = { range = nil, -- temproal range limits the gesture emitting rate rate = nil, + -- span limits of this gesture + span = nil, } function GestureRange:new(o) @@ -19,24 +21,27 @@ function GestureRange:match(gs) if gs.ges ~= self.ges then return false end - - if self.range:contains(gs.pos) then - if self.rate then - local last_time = self.last_time or TimeVal:new{} - if gs.time - last_time > TimeVal:new{usec = 1000000 / self.rate} then - self.last_time = gs.time - return true - else - return false - end + if self.range then + if not self.range:contains(gs.pos) then + return false end - return true end - - return false + if self.rate then + local last_time = self.last_time or TimeVal:new{} + if gs.time - last_time > TimeVal:new{usec = 1000000 / self.rate} then + self.last_time = gs.time + else + return false + end + end + if self.span then + if self.span[1] > gs.span or self.span[2] < gs.span then + return false + end + end + return true end - --[[ Currently supported gestures: * single tap @@ -66,10 +71,12 @@ feed a touch release event to it. GestureDetector = { -- all the time parameters are in us DOUBLE_TAP_INTERVAL = 300 * 1000, + TWO_FINGER_TAP_DURATION = 300 * 1000, HOLD_INTERVAL = 1000 * 1000, SWIPE_INTERVAL = 900 * 1000, -- distance parameters DOUBLE_TAP_DISTANCE = 50, + TWO_FINGER_TAP_REGION = 20, PAN_THRESHOLD = 50, -- states are stored in separated slots @@ -85,16 +92,22 @@ GestureDetector = { last_taps = {}, } -function GestureDetector:feedEvent(tev) - local slot = tev.slot - if not self.states[slot] then - self:clearState(slot) -- initiate state - end - local ges = self.states[slot](self, tev) - if tev.id ~= -1 then - self.last_tevs[slot] = tev - end - return ges +function GestureDetector:feedEvent(tevs) + repeat + local tev = table.remove(tevs) + if tev then + local slot = tev.slot + if not self.states[slot] then + self:clearState(slot) -- initiate state + end + local ges = self.states[slot](self, tev) + if tev.id ~= -1 then + self.last_tevs[slot] = tev + end + -- return no more than one gesture + if ges then return ges end + end + until tev == nil end function GestureDetector:deepCopyEv(tev) @@ -122,6 +135,23 @@ function GestureDetector:isDoubleTap(tap1, tap2) ) end +function GestureDetector:isTwoFingerTap(tev0, tev1) + local x_diff0 = math.abs(tev0.x - self.first_tevs[0].x) + local x_diff1 = math.abs(tev1.x - self.first_tevs[1].x) + local y_diff0 = math.abs(tev0.y - self.first_tevs[0].y) + local y_diff1 = math.abs(tev1.y - self.first_tevs[1].y) + local tv_diff0 = tev0.timev - self.first_tevs[0].timev + local tv_diff1 = tev1.timev - self.first_tevs[1].timev + return ( + x_diff0 < self.TWO_FINGER_TAP_REGION and + x_diff1 < self.TWO_FINGER_TAP_REGION and + y_diff0 < self.TWO_FINGER_TAP_REGION and + y_diff1 < self.TWO_FINGER_TAP_REGION and + tv_diff0.sec == 0 and tv_diff0.usec < self.TWO_FINGER_TAP_DURATION and + tv_diff1.sec == 0 and tv_diff1.usec < self.TWO_FINGER_TAP_DURATION + ) +end + --[[ compare last_pan with first_tev in this slot return pan direction and distance @@ -164,9 +194,9 @@ end function GestureDetector:clearState(slot) self.states[slot] = self.initialState - self.last_tevs[slot] = {} self.detectings[slot] = false self.first_tevs[slot] = nil + self.last_tevs[slot] = nil end function GestureDetector:initialState(tev) @@ -194,59 +224,100 @@ end this method handles both single and double tap --]] function GestureDetector:tapState(tev) - DEBUG("in tap state...") + DEBUG("in tap state...", tev) local slot = tev.slot - if tev.id == -1 then - -- end of tap event - local ges_ev = { - -- default to single tap - ges = "tap", - pos = Geom:new{ - x = self.last_tevs[slot].x, - y = self.last_tevs[slot].y, - w = 0, h = 0, - }, - time = tev.timev, - } - -- cur_tap is used for double tap detection - local cur_tap = { - x = tev.x, - y = tev.y, - timev = tev.timev, - } - - if self.last_taps[slot] ~= nil and - self:isDoubleTap(self.last_taps[slot], cur_tap) then - -- it is a double tap - self:clearState(slot) - ges_ev.ges = "double_tap" - self.last_taps[slot] = nil - DEBUG("double tap detected in slot", slot) - return ges_ev + if slot == 1 then + if tev.id == -1 and self.last_tevs[0] ~= nil then + if self:isTwoFingerTap(self.last_tevs[0], tev) then + local pos0 = Geom:new{ + x = self.last_tevs[0].x, + y = self.last_tevs[0].y, + w = 0, h = 0, + } + local pos1 = Geom:new{ + x = tev.x, + y = tev.y, + w = 0, h = 0, + } + local ges_ev = { + ges = "two_finger_tap", + span = pos0:distance(pos1), + time = tev.timev, + } + DEBUG("two-finger tap detected") + self:clearState(0) + self:clearState(1) + return ges_ev + else + self:clearState(0) + self:clearState(1) + end end - - -- set current tap to last tap - self.last_taps[slot] = cur_tap - - DEBUG("set up tap timer") - -- deadline should be calculated by adding current tap time and the interval - local deadline = cur_tap.timev + TimeVal:new{ - sec = 0, usec = self.DOUBLE_TAP_INTERVAL, - } - Input:setTimeout(function() - DEBUG("in tap timer", self.last_taps[slot] ~= nil) - -- double tap will set last_tap to nil so if it is not, then - -- user must only tapped once - if self.last_taps[slot] ~= nil then + elseif tev.id == -1 then + -- end of tap event + if self.last_tevs[slot] ~= nil then + local ges_ev = { + -- default to single tap + ges = "tap", + pos = Geom:new{ + x = self.last_tevs[slot].x, + y = self.last_tevs[slot].y, + w = 0, h = 0, + }, + time = tev.timev, + } + -- cur_tap is used for double tap detection + local cur_tap = { + x = tev.x, + y = tev.y, + timev = tev.timev, + } + + if self.last_taps[slot] ~= nil and + self:isDoubleTap(self.last_taps[slot], cur_tap) then + -- it is a double tap + self:clearState(slot) + ges_ev.ges = "double_tap" self.last_taps[slot] = nil - -- we are using closure here - DEBUG("single tap detected in slot", slot) + DEBUG("double tap detected in slot", slot) return ges_ev end - end, deadline) - -- we are already at the end of touch event - -- so reset the state - self:clearState(slot) + + -- set current tap to last tap + self.last_taps[slot] = cur_tap + + DEBUG("set up tap timer") + -- deadline should be calculated by adding current tap time and the interval + local deadline = cur_tap.timev + TimeVal:new{ + sec = 0, usec = self.DOUBLE_TAP_INTERVAL, + } + Input:setTimeout(function() + DEBUG("in tap timer", self.last_taps[slot] ~= nil) + -- double tap will set last_tap to nil so if it is not, then + -- user must only tapped once + if self.last_taps[slot] ~= nil then + self.last_taps[slot] = nil + -- we are using closure here + DEBUG("single tap detected in slot", slot) + return ges_ev + end + end, deadline) + -- we are already at the end of touch event + -- so reset the state + self:clearState(slot) + else + -- last tev in this slot is cleared by last two finger tap + self:clearState(slot) + return { + ges = "tap", + pos = Geom:new{ + x = tev.x, + y = tev.y, + w = 0, h = 0, + }, + time = tev.timev, + } + end elseif self.states[slot] ~= self.tapState then -- switched from other state, probably from initialState -- we return nil in this case @@ -342,7 +413,7 @@ function GestureDetector:holdState(tev, hold) time = tev.timev, } end - if tev.id == -1 then + if tev.id == -1 and self.last_tevs[slot] ~= nil then -- end of hold, signal hold release DEBUG("hold_release detected in slot", slot) local last_x = self.last_tevs[slot].x @@ -369,7 +440,9 @@ end function GestureDetector:adjustGesCoordinate(ges) if Screen.cur_rotation_mode == 1 then -- in landscape mode - ges.pos.x, ges.pos.y = (Screen.width - ges.pos.y), (ges.pos.x) + if ges.pos then + ges.pos.x, ges.pos.y = (Screen.width - ges.pos.y), (ges.pos.x) + end if ges.ges == "swipe" then if ges.direction == "down" then ges.direction = "left"