diff --git a/frontend/ui/gesturedetector.lua b/frontend/ui/gesturedetector.lua index 4efc4883e..15cd8998d 100644 --- a/frontend/ui/gesturedetector.lua +++ b/frontend/ui/gesturedetector.lua @@ -1,3 +1,4 @@ +require "ui/inputevent" require "ui/geometry" -- Synchronization events (SYN.code). @@ -12,6 +13,7 @@ ABS_MT_POSITION_Y = 54 ABS_MT_TRACKING_ID = 57 ABS_MT_PRESSURE = 58 + GestureRange = { ges = nil, range = nil, @@ -30,20 +32,15 @@ function GestureRange:match(gs) end if self.range:contains(gs.pos) then - DEBUG(self, gs) return true end return false end + --[[ -MT_TRACK_ID: 0 -MT_X: 310 -MT_Y: 174 -SYN REPORT -MT_TRACK_ID: -1 -SYN REPORT +Single tap event from kernel example: MT_TRACK_ID: 0 MT_X: 222 @@ -54,18 +51,32 @@ SYN REPORT --]] GestureDetector = { + -- all the time parameters are in ms + DOUBLE_TAP_TIME = 500, + -- distance parameters + DOUBLE_TAP_DISTANCE = 50, + PAN_THRESHOLD = 50, + track_id = {}, ev_stack = {}, cur_ev = {}, - state = function(self) - self.state = self.initialState + ev_start = false, + state = function(self, ev) + self.switchState("initialState", ev) end, + + last_ev_time = nil, + + -- for tap + last_tap = nil, } function GestureDetector:feedEvent(ev) if ev.type == EV_SYN then if ev.code == SYN_REPORT then + self.cur_ev.time = ev.time local re = self.state(self, self.cur_ev) + self.last_ev_time = ev.time if re ~= nil then return re end @@ -84,48 +95,141 @@ function GestureDetector:feedEvent(ev) end end -function GestureDetector:initialState(ev) - if ev.id then - self.track_id[ev.id] = ev.slot - end - -- default to hold state - if ev.x and ev.y then - self.hold_x = ev.x - self.hold_y = ev.y - self.state = self.holdState - end +--[[ +tap2 is the later tap +]] +function GestureDetector:isDoubleTap(tap1, tap2) + --@TODO this is a bug (houqp) + local msec_diff = (tap2.time.usec - tap1.time.usec) * 1000 + return ( + math.abs(tap1.x - tap2.x) < self.DOUBLE_TAP_DISTANCE and + math.abs(tap1.y - tap2.y) < self.DOUBLE_TAP_DISTANCE and + msec_diff < self.DOUBLE_TAP_TIME + ) end -function GestureDetector:holdState(ev) - -- hold release, we send a tap event? - if ev.id == -1 then - local hold_x, hold_y = self.hold_x, self.hold_y - self:clearState() - return { - ges = "tap", - pos = Geom:new{ - x = hold_x, - y = hold_y, - w = 0, h = 0, - } - } - elseif ev.x and ev.y then - self.hold_x = ev.x - self.hold_y = ev.y - return { - ges = "hold", - pos = Geom:new{ - x = self.hold_x, - y = self.hold_y, - w = 0, h = 0, - } - } - end +function GestureDetector:switchState(state, ev) + --@TODO do we need to check whether state is valid? (houqp) + return self[state](self, ev) end function GestureDetector:clearState() - self.hold_x = nil - self.hold_y = nil + self.cur_x = nil + self.cur_y = nil self.state = self.initialState self.cur_ev = {} + self.ev_start = false end + +function GestureDetector:initialState(ev) + if ev.id then + -- a event ends + if ev.id == -1 then + self.ev_start = false + else + self.track_id[ev.id] = ev.slot + end + end + if ev.x and ev.y then + -- a new event has just started + if not self.ev_start then + self.ev_start = true + -- default to tap state + return self:switchState("tapState", ev) + end + end +end + +--[[ +this method handles both single and double tap +]] +function GestureDetector:tapState(ev) + if ev.id == -1 then + -- end of tap event + local ges_ev = { + -- default to single tap + ges = "tap", + pos = Geom:new{ + x = self.cur_x, + y = self.cur_y, + w = 0, h = 0, + } + } + -- cur_tap is used for double tap detection + local cur_tap = { + x = self.cur_x, + y = self.cur_y, + time = ev.time, + } + + if self.last_tap and + self:isDoubleTap(self.last_tap, cur_tap) then + ges_ev.ges = "double_tap" + self.last_tap = nil + end + + if ges_ev.ges == "tap" then + -- set current tap to last tap + self.last_tap = cur_tap + end + + self:clearState() + return ges_ev + elseif self.state ~= self.tapState then + -- switched from other state, probably from initialState + -- we return nil in this case + self.state = self.tapState + self.cur_x = ev.x + self.cur_y = ev.y + --@TODO set up hold timer (houqp) + table.insert(Input.timer_callbacks, { + callback = function() + if self.state == self.tapState then + -- timer set in tapState, so we switch to hold + return self.switchState("holdState") + end + end, + time = ev.time, + time_out = HOLD_TIME + }) + else + -- it is not end of touch event, see if we need to switch to + -- other states + if math.abs(ev.x - self.cur_x) >= self.PAN_THRESHOLD or + math.abs(ev.y - self.cur_y) >= self.PAN_THRESHOLD then + -- if user's finger moved long enough in X or + -- Y distance, we switch to pan state + return self:switchState("panState", ev) + end + end +end + +function GestureDetector:panState(ev) + if ev.id == -1 then + -- end of pan, signal swipe gesture + elseif self.state ~= self.panState then + self.state = self.panState + --@TODO calculate direction here (houqp) + else + end + self.cur_x = ev.x + self.cur_y = ev.y +end + +function GestureDetector:holdState(ev) + if not ev and self.cur_x and self.cur_y then + return { + ges = "hold", + pos = Geom:new{ + x = self.cur_x, + y = self.cur_y, + w = 0, h = 0, + } + } + end + if ev.id == -1 then + -- end of hold, signal hold release? + end +end + +