From 7514a5686a83807ceafa33bb7f1caa701d1c5d6b Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sun, 11 Nov 2012 01:00:52 -0500 Subject: [PATCH 01/14] first demo of gesture parsing --- frontend/ui/geometry.lua | 4 +- frontend/ui/gesturedetector.lua | 115 ++++++++++++++++++++++++++++++++ frontend/ui/inputevent.lua | 63 ++++++++--------- frontend/ui/menu.lua | 24 ++++++- frontend/ui/widget.lua | 26 +++++++- 5 files changed, 193 insertions(+), 39 deletions(-) create mode 100644 frontend/ui/gesturedetector.lua diff --git a/frontend/ui/geometry.lua b/frontend/ui/geometry.lua index 847569aaa..1a45936a4 100644 --- a/frontend/ui/geometry.lua +++ b/frontend/ui/geometry.lua @@ -146,8 +146,8 @@ for points, it is basically an equality check function Geom:contains(rect_b) if self.x <= rect_b.x and self.y <= rect_b.y - and self.x + self.w <= rect_b.x + rect_b.w - and self.y + self.h <= rect_b.y + rect_b.h + and self.x + self.w >= rect_b.x + rect_b.w + and self.y + self.h >= rect_b.y + rect_b.h then return true end diff --git a/frontend/ui/gesturedetector.lua b/frontend/ui/gesturedetector.lua new file mode 100644 index 000000000..4d7eb8834 --- /dev/null +++ b/frontend/ui/gesturedetector.lua @@ -0,0 +1,115 @@ +require "ui/geometry" + +-- Synchronization events (SYN.code). +SYN_REPORT = 0 +SYN_CONFIG = 1 +SYN_MT_REPORT = 2 + +-- For multi-touch events (ABS.code). +ABS_MT_SLOT = 47 +ABS_MT_POSITION_X = 53 +ABS_MT_POSITION_Y = 54 +ABS_MT_TRACKING_ID = 57 +ABS_MT_PRESSURE = 58 + +GestureRange = { + ges = nil, + range = nil, +} + +function GestureRange:new(o) + local o = o or {} + setmetatable(o, self) + self.__index = self + return o +end + +function GestureRange:match(gs) + if gs.ges ~= self.ges then + return false + end + + if self.range:contains(gs.pos) then + DEBUG(self, gs) + return true + end + + return false +end + +GestureDetector = { + ev_stack = {}, + cur_ev = {}, +} + +--[[ +MT_TRACK_ID: 0 +MT_X: 310 +MT_Y: 174 +SYN REPORT +MT_TRACK_ID: -1 +SYN REPORT + +MT_TRACK_ID: 0 +MT_X: 222 +MT_Y: 207 +SYN REPORT +MT_TRACK_ID: -1 +SYN REPORT +--]] + +function GestureDetector:feedEvent(ev) + if ev.type == EV_SYN then + if ev.code == SYN_REPORT then + -- end of one event or release touch? + if self.cur_ev.id == -1 then + -- touch release? + return self:guessGesture() + else + table.insert(self.ev_stack, self.cur_ev) + self.cur_ev = {} + --DEBUG(self.ev_stack) + end + end + elseif ev.type == EV_ABS then + if ev.code == ABS_MT_SLOT then + elseif ev.code == ABS_MT_TRACKING_ID then + self.cur_ev.id = ev.value + elseif ev.code == ABS_MT_POSITION_X then + self.cur_ev.x = ev.value + elseif ev.code == ABS_MT_POSITION_Y then + self.cur_ev.y = ev.value + end + end +end + +function GestureDetector:guessGesture() + local is_recognized = false + local result = nil + local last_ev = {pos = Geom:new{}} + + for k,ev in ipairs(self.ev_stack) do + --@TODO do real recognization here (houqp) + is_recognized = true + result = { + ges = "tap", + pos = Geom:new{ + x = ev.x or last_ev.x, + y = ev.y or last_ev.x, + w = 0, + h = 0, + } + } + last_ev = ev + end + + if is_recognized then + self.ev_stack = {} + return result + else + DEBUG("Unknown gesture!!", self.ev_stack) + self.ev_stack = {} + end +end + + diff --git a/frontend/ui/inputevent.lua b/frontend/ui/inputevent.lua index 41afe6b9a..4e5cd1baa 100644 --- a/frontend/ui/inputevent.lua +++ b/frontend/ui/inputevent.lua @@ -1,5 +1,6 @@ require "ui/event" require "ui/device" +require "ui/gesturedetector" require "settings" -- constants from @@ -7,18 +8,6 @@ EV_SYN = 0 EV_KEY = 1 EV_ABS = 3 --- Synchronization events (SYN.code). -SYN_REPORT = 0 -SYN_CONFIG = 1 -SYN_MT_REPORT = 2 - --- For multi-touch events (ABS.code). -ABS_MT_SLOT = 47 -ABS_MT_POSITION_X = 53 -ABS_MT_POSITION_Y = 54 -ABS_MT_TRACKING_ID = 57 -ABS_MT_PRESSURE = 58 - -- key press event values (KEY.value) EVENT_VALUE_KEY_PRESS = 1 EVENT_VALUE_KEY_REPEAT = 2 @@ -341,29 +330,35 @@ function Input:waitEvent(timeout_us, timeout_s) elseif ev.value == EVENT_VALUE_KEY_RELEASE then return Event:new("KeyRelease", key) end - elseif ev.type == EV_ABS then - if ev.code == ABS_MT_SLOT then - DEBUG("MT_SLOT:", ev.value) - elseif ev.code == ABS_MT_TRACKING_ID then - DEBUG("MT_TRACK_ID:", ev.value) - elseif ev.code == ABS_MT_POSITION_X then - DEBUG("MT_X:", ev.value) - elseif ev.code == ABS_MT_POSITION_Y then - DEBUG("MT_Y:", ev.value) - else - DEBUG("unknown touch event!", ev) - return Event:new("UnkonwnTouchEvent", ev) - end - elseif ev.type == EV_SYN then - if ev.code == SYN_REPORT then - DEBUG("SYN REPORT") - elseif ev.code == SYN_MT_REPORT then - DEBUG("SYN MT_REPORT") - elseif ev.code == SYN_CONFIG then - DEBUG("SYN CONFIG") - else - DEBUG(ev) + elseif ev.type == EV_ABS or ev.type == EV_SYN then + local touch_ges = GestureDetector:feedEvent(ev) + DEBUG(touch_ges) + if touch_ges then + return Event:new("Gesture", touch_ges) end + --elseif ev.type == EV_ABS then + --if ev.code == ABS_MT_SLOT then + --DEBUG("MT_SLOT:", ev.value) + --elseif ev.code == ABS_MT_TRACKING_ID then + --DEBUG("MT_TRACK_ID:", ev.value) + --elseif ev.code == ABS_MT_POSITION_X then + --DEBUG("MT_X:", ev.value) + --elseif ev.code == ABS_MT_POSITION_Y then + --DEBUG("MT_Y:", ev.value) + --else + --DEBUG("unknown touch event!", ev) + --return Event:new("UnkonwnTouchEvent", ev) + --end + --elseif ev.type == EV_SYN then + --if ev.code == SYN_REPORT then + --DEBUG("SYN REPORT") + --elseif ev.code == SYN_MT_REPORT then + --DEBUG("SYN MT_REPORT") + --elseif ev.code == SYN_CONFIG then + --DEBUG("SYN CONFIG") + --else + --DEBUG(ev) + --end else -- some other kind of event that we do not know yet return Event:new("GenericInput", ev) diff --git a/frontend/ui/menu.lua b/frontend/ui/menu.lua index 30498b9bd..93776dd0b 100644 --- a/frontend/ui/menu.lua +++ b/frontend/ui/menu.lua @@ -80,6 +80,21 @@ function MenuItem:init() self.active_key_events = { Select = { {"Press"}, doc = "chose selected item" }, } + self.ges_events = { + TapSelect = { + GestureRange:new{ + ges = "tap", + range = self.dimen, + --range = Geom:new{ + --x = self.dimen.x, + --y = self.dimen.y, + --h = self.dimen.h, + --w = self.dimen.w, + --}, + }, + doc = "Select Menu Item", + }, + } w = sizeUtf8Text(0, self.dimen.w, self.face, self.text, true).x if w >= self.content_width then @@ -136,6 +151,11 @@ function MenuItem:onShowItemDetail() return true end +function MenuItem:onTapSelect() + self.menu:onMenuSelect(self.table) + return true +end + --[[ Widget that displays menu @@ -265,9 +285,11 @@ function Menu:updateItems(select_number) local item_tmp = MenuItem:new{ text = self.item_table[i].text, face = self.cface, - dimen = self.item_dimen, + dimen = self.item_dimen:new(), shortcut = item_shortcut, shortcut_style = shortcut_style, + table = self.item_table[i], + menu = self, } table.insert(self.item_group, item_tmp) table.insert(self.layout, {item_tmp}) diff --git a/frontend/ui/widget.lua b/frontend/ui/widget.lua index 630fa0589..bc23b144f 100644 --- a/frontend/ui/widget.lua +++ b/frontend/ui/widget.lua @@ -4,6 +4,7 @@ require "ui/graphics" require "ui/image" require "ui/event" require "ui/inputevent" +require "ui/gesturedetector" require "ui/font" --[[ @@ -60,7 +61,7 @@ function WidgetContainer:getSize() -- return size of first child widget return self[1]:getSize() else - return { w = 0, h = 0 } + return Geom:new{ w = 0, h = 0 } end end @@ -513,9 +514,18 @@ it is suggested to reference configurable sequences from another table and store that table as configuration setting ]] InputContainer = WidgetContainer:new{ - key_events = {} + key_events = {}, + ges_events = {}, } +function InputContainer:paintTo(bb, x, y) + self.dimen.x = x + self.dimen.y = y + if self[1] then + return self[1]:paintTo(bb, x, y) + end +end + -- the following handler handles keypresses and checks -- if they lead to a command. -- if this is the case, we retransmit another event within @@ -533,3 +543,15 @@ function InputContainer:onKeyPress(key) end end +function InputContainer:onGesture(ev) + for name, gsseq in pairs(self.ges_events) do + for _, gs_range in ipairs(gsseq) do + if gs_range:match(ev) then + --DEBUG(gs_range) + local eventname = gsseq.event or name + return self:handleEvent(Event:new(eventname, gsseq.args, ev)) + end + end + end +end + From d8568520c0f31898f641fd6e9d620ecad9c097ba Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sun, 11 Nov 2012 14:19:07 +0800 Subject: [PATCH 02/14] bug fix in single tap detection --- frontend/ui/gesturedetector.lua | 19 +++++++------------ frontend/ui/inputevent.lua | 23 ----------------------- 2 files changed, 7 insertions(+), 35 deletions(-) diff --git a/frontend/ui/gesturedetector.lua b/frontend/ui/gesturedetector.lua index 4d7eb8834..3f04902d8 100644 --- a/frontend/ui/gesturedetector.lua +++ b/frontend/ui/gesturedetector.lua @@ -85,22 +85,17 @@ end function GestureDetector:guessGesture() local is_recognized = false - local result = nil - local last_ev = {pos = Geom:new{}} + local result = {ges = "tap", pos = Geom:new{w=0, h=0}} for k,ev in ipairs(self.ev_stack) do --@TODO do real recognization here (houqp) is_recognized = true - result = { - ges = "tap", - pos = Geom:new{ - x = ev.x or last_ev.x, - y = ev.y or last_ev.x, - w = 0, - h = 0, - } - } - last_ev = ev + if ev.x then + result.pos.x = ev.x + end + if ev.y then + result.pos.y = ev.y + end end if is_recognized then diff --git a/frontend/ui/inputevent.lua b/frontend/ui/inputevent.lua index 4e5cd1baa..dc3cac74a 100644 --- a/frontend/ui/inputevent.lua +++ b/frontend/ui/inputevent.lua @@ -336,29 +336,6 @@ function Input:waitEvent(timeout_us, timeout_s) if touch_ges then return Event:new("Gesture", touch_ges) end - --elseif ev.type == EV_ABS then - --if ev.code == ABS_MT_SLOT then - --DEBUG("MT_SLOT:", ev.value) - --elseif ev.code == ABS_MT_TRACKING_ID then - --DEBUG("MT_TRACK_ID:", ev.value) - --elseif ev.code == ABS_MT_POSITION_X then - --DEBUG("MT_X:", ev.value) - --elseif ev.code == ABS_MT_POSITION_Y then - --DEBUG("MT_Y:", ev.value) - --else - --DEBUG("unknown touch event!", ev) - --return Event:new("UnkonwnTouchEvent", ev) - --end - --elseif ev.type == EV_SYN then - --if ev.code == SYN_REPORT then - --DEBUG("SYN REPORT") - --elseif ev.code == SYN_MT_REPORT then - --DEBUG("SYN MT_REPORT") - --elseif ev.code == SYN_CONFIG then - --DEBUG("SYN CONFIG") - --else - --DEBUG(ev) - --end else -- some other kind of event that we do not know yet return Event:new("GenericInput", ev) From 3486ae7e09c9638ba533c32672651dd62fed820c Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sun, 11 Nov 2012 01:00:52 -0500 Subject: [PATCH 03/14] first demo of gesture parsing --- frontend/ui/geometry.lua | 4 +- frontend/ui/gesturedetector.lua | 115 ++++++++++++++++++++++++++++++++ frontend/ui/inputevent.lua | 63 ++++++++--------- frontend/ui/menu.lua | 24 ++++++- frontend/ui/widget.lua | 26 +++++++- 5 files changed, 193 insertions(+), 39 deletions(-) create mode 100644 frontend/ui/gesturedetector.lua diff --git a/frontend/ui/geometry.lua b/frontend/ui/geometry.lua index 847569aaa..1a45936a4 100644 --- a/frontend/ui/geometry.lua +++ b/frontend/ui/geometry.lua @@ -146,8 +146,8 @@ for points, it is basically an equality check function Geom:contains(rect_b) if self.x <= rect_b.x and self.y <= rect_b.y - and self.x + self.w <= rect_b.x + rect_b.w - and self.y + self.h <= rect_b.y + rect_b.h + and self.x + self.w >= rect_b.x + rect_b.w + and self.y + self.h >= rect_b.y + rect_b.h then return true end diff --git a/frontend/ui/gesturedetector.lua b/frontend/ui/gesturedetector.lua new file mode 100644 index 000000000..4d7eb8834 --- /dev/null +++ b/frontend/ui/gesturedetector.lua @@ -0,0 +1,115 @@ +require "ui/geometry" + +-- Synchronization events (SYN.code). +SYN_REPORT = 0 +SYN_CONFIG = 1 +SYN_MT_REPORT = 2 + +-- For multi-touch events (ABS.code). +ABS_MT_SLOT = 47 +ABS_MT_POSITION_X = 53 +ABS_MT_POSITION_Y = 54 +ABS_MT_TRACKING_ID = 57 +ABS_MT_PRESSURE = 58 + +GestureRange = { + ges = nil, + range = nil, +} + +function GestureRange:new(o) + local o = o or {} + setmetatable(o, self) + self.__index = self + return o +end + +function GestureRange:match(gs) + if gs.ges ~= self.ges then + return false + end + + if self.range:contains(gs.pos) then + DEBUG(self, gs) + return true + end + + return false +end + +GestureDetector = { + ev_stack = {}, + cur_ev = {}, +} + +--[[ +MT_TRACK_ID: 0 +MT_X: 310 +MT_Y: 174 +SYN REPORT +MT_TRACK_ID: -1 +SYN REPORT + +MT_TRACK_ID: 0 +MT_X: 222 +MT_Y: 207 +SYN REPORT +MT_TRACK_ID: -1 +SYN REPORT +--]] + +function GestureDetector:feedEvent(ev) + if ev.type == EV_SYN then + if ev.code == SYN_REPORT then + -- end of one event or release touch? + if self.cur_ev.id == -1 then + -- touch release? + return self:guessGesture() + else + table.insert(self.ev_stack, self.cur_ev) + self.cur_ev = {} + --DEBUG(self.ev_stack) + end + end + elseif ev.type == EV_ABS then + if ev.code == ABS_MT_SLOT then + elseif ev.code == ABS_MT_TRACKING_ID then + self.cur_ev.id = ev.value + elseif ev.code == ABS_MT_POSITION_X then + self.cur_ev.x = ev.value + elseif ev.code == ABS_MT_POSITION_Y then + self.cur_ev.y = ev.value + end + end +end + +function GestureDetector:guessGesture() + local is_recognized = false + local result = nil + local last_ev = {pos = Geom:new{}} + + for k,ev in ipairs(self.ev_stack) do + --@TODO do real recognization here (houqp) + is_recognized = true + result = { + ges = "tap", + pos = Geom:new{ + x = ev.x or last_ev.x, + y = ev.y or last_ev.x, + w = 0, + h = 0, + } + } + last_ev = ev + end + + if is_recognized then + self.ev_stack = {} + return result + else + DEBUG("Unknown gesture!!", self.ev_stack) + self.ev_stack = {} + end +end + + diff --git a/frontend/ui/inputevent.lua b/frontend/ui/inputevent.lua index 41afe6b9a..4e5cd1baa 100644 --- a/frontend/ui/inputevent.lua +++ b/frontend/ui/inputevent.lua @@ -1,5 +1,6 @@ require "ui/event" require "ui/device" +require "ui/gesturedetector" require "settings" -- constants from @@ -7,18 +8,6 @@ EV_SYN = 0 EV_KEY = 1 EV_ABS = 3 --- Synchronization events (SYN.code). -SYN_REPORT = 0 -SYN_CONFIG = 1 -SYN_MT_REPORT = 2 - --- For multi-touch events (ABS.code). -ABS_MT_SLOT = 47 -ABS_MT_POSITION_X = 53 -ABS_MT_POSITION_Y = 54 -ABS_MT_TRACKING_ID = 57 -ABS_MT_PRESSURE = 58 - -- key press event values (KEY.value) EVENT_VALUE_KEY_PRESS = 1 EVENT_VALUE_KEY_REPEAT = 2 @@ -341,29 +330,35 @@ function Input:waitEvent(timeout_us, timeout_s) elseif ev.value == EVENT_VALUE_KEY_RELEASE then return Event:new("KeyRelease", key) end - elseif ev.type == EV_ABS then - if ev.code == ABS_MT_SLOT then - DEBUG("MT_SLOT:", ev.value) - elseif ev.code == ABS_MT_TRACKING_ID then - DEBUG("MT_TRACK_ID:", ev.value) - elseif ev.code == ABS_MT_POSITION_X then - DEBUG("MT_X:", ev.value) - elseif ev.code == ABS_MT_POSITION_Y then - DEBUG("MT_Y:", ev.value) - else - DEBUG("unknown touch event!", ev) - return Event:new("UnkonwnTouchEvent", ev) - end - elseif ev.type == EV_SYN then - if ev.code == SYN_REPORT then - DEBUG("SYN REPORT") - elseif ev.code == SYN_MT_REPORT then - DEBUG("SYN MT_REPORT") - elseif ev.code == SYN_CONFIG then - DEBUG("SYN CONFIG") - else - DEBUG(ev) + elseif ev.type == EV_ABS or ev.type == EV_SYN then + local touch_ges = GestureDetector:feedEvent(ev) + DEBUG(touch_ges) + if touch_ges then + return Event:new("Gesture", touch_ges) end + --elseif ev.type == EV_ABS then + --if ev.code == ABS_MT_SLOT then + --DEBUG("MT_SLOT:", ev.value) + --elseif ev.code == ABS_MT_TRACKING_ID then + --DEBUG("MT_TRACK_ID:", ev.value) + --elseif ev.code == ABS_MT_POSITION_X then + --DEBUG("MT_X:", ev.value) + --elseif ev.code == ABS_MT_POSITION_Y then + --DEBUG("MT_Y:", ev.value) + --else + --DEBUG("unknown touch event!", ev) + --return Event:new("UnkonwnTouchEvent", ev) + --end + --elseif ev.type == EV_SYN then + --if ev.code == SYN_REPORT then + --DEBUG("SYN REPORT") + --elseif ev.code == SYN_MT_REPORT then + --DEBUG("SYN MT_REPORT") + --elseif ev.code == SYN_CONFIG then + --DEBUG("SYN CONFIG") + --else + --DEBUG(ev) + --end else -- some other kind of event that we do not know yet return Event:new("GenericInput", ev) diff --git a/frontend/ui/menu.lua b/frontend/ui/menu.lua index 30498b9bd..93776dd0b 100644 --- a/frontend/ui/menu.lua +++ b/frontend/ui/menu.lua @@ -80,6 +80,21 @@ function MenuItem:init() self.active_key_events = { Select = { {"Press"}, doc = "chose selected item" }, } + self.ges_events = { + TapSelect = { + GestureRange:new{ + ges = "tap", + range = self.dimen, + --range = Geom:new{ + --x = self.dimen.x, + --y = self.dimen.y, + --h = self.dimen.h, + --w = self.dimen.w, + --}, + }, + doc = "Select Menu Item", + }, + } w = sizeUtf8Text(0, self.dimen.w, self.face, self.text, true).x if w >= self.content_width then @@ -136,6 +151,11 @@ function MenuItem:onShowItemDetail() return true end +function MenuItem:onTapSelect() + self.menu:onMenuSelect(self.table) + return true +end + --[[ Widget that displays menu @@ -265,9 +285,11 @@ function Menu:updateItems(select_number) local item_tmp = MenuItem:new{ text = self.item_table[i].text, face = self.cface, - dimen = self.item_dimen, + dimen = self.item_dimen:new(), shortcut = item_shortcut, shortcut_style = shortcut_style, + table = self.item_table[i], + menu = self, } table.insert(self.item_group, item_tmp) table.insert(self.layout, {item_tmp}) diff --git a/frontend/ui/widget.lua b/frontend/ui/widget.lua index 630fa0589..bc23b144f 100644 --- a/frontend/ui/widget.lua +++ b/frontend/ui/widget.lua @@ -4,6 +4,7 @@ require "ui/graphics" require "ui/image" require "ui/event" require "ui/inputevent" +require "ui/gesturedetector" require "ui/font" --[[ @@ -60,7 +61,7 @@ function WidgetContainer:getSize() -- return size of first child widget return self[1]:getSize() else - return { w = 0, h = 0 } + return Geom:new{ w = 0, h = 0 } end end @@ -513,9 +514,18 @@ it is suggested to reference configurable sequences from another table and store that table as configuration setting ]] InputContainer = WidgetContainer:new{ - key_events = {} + key_events = {}, + ges_events = {}, } +function InputContainer:paintTo(bb, x, y) + self.dimen.x = x + self.dimen.y = y + if self[1] then + return self[1]:paintTo(bb, x, y) + end +end + -- the following handler handles keypresses and checks -- if they lead to a command. -- if this is the case, we retransmit another event within @@ -533,3 +543,15 @@ function InputContainer:onKeyPress(key) end end +function InputContainer:onGesture(ev) + for name, gsseq in pairs(self.ges_events) do + for _, gs_range in ipairs(gsseq) do + if gs_range:match(ev) then + --DEBUG(gs_range) + local eventname = gsseq.event or name + return self:handleEvent(Event:new(eventname, gsseq.args, ev)) + end + end + end +end + From 032a71cbc462d9dd1f47aabe135f1fb93b7a9470 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sun, 11 Nov 2012 14:19:07 +0800 Subject: [PATCH 04/14] bug fix in single tap detection --- frontend/ui/gesturedetector.lua | 19 +++++++------------ frontend/ui/inputevent.lua | 23 ----------------------- 2 files changed, 7 insertions(+), 35 deletions(-) diff --git a/frontend/ui/gesturedetector.lua b/frontend/ui/gesturedetector.lua index 4d7eb8834..3f04902d8 100644 --- a/frontend/ui/gesturedetector.lua +++ b/frontend/ui/gesturedetector.lua @@ -85,22 +85,17 @@ end function GestureDetector:guessGesture() local is_recognized = false - local result = nil - local last_ev = {pos = Geom:new{}} + local result = {ges = "tap", pos = Geom:new{w=0, h=0}} for k,ev in ipairs(self.ev_stack) do --@TODO do real recognization here (houqp) is_recognized = true - result = { - ges = "tap", - pos = Geom:new{ - x = ev.x or last_ev.x, - y = ev.y or last_ev.x, - w = 0, - h = 0, - } - } - last_ev = ev + if ev.x then + result.pos.x = ev.x + end + if ev.y then + result.pos.y = ev.y + end end if is_recognized then diff --git a/frontend/ui/inputevent.lua b/frontend/ui/inputevent.lua index 4e5cd1baa..dc3cac74a 100644 --- a/frontend/ui/inputevent.lua +++ b/frontend/ui/inputevent.lua @@ -336,29 +336,6 @@ function Input:waitEvent(timeout_us, timeout_s) if touch_ges then return Event:new("Gesture", touch_ges) end - --elseif ev.type == EV_ABS then - --if ev.code == ABS_MT_SLOT then - --DEBUG("MT_SLOT:", ev.value) - --elseif ev.code == ABS_MT_TRACKING_ID then - --DEBUG("MT_TRACK_ID:", ev.value) - --elseif ev.code == ABS_MT_POSITION_X then - --DEBUG("MT_X:", ev.value) - --elseif ev.code == ABS_MT_POSITION_Y then - --DEBUG("MT_Y:", ev.value) - --else - --DEBUG("unknown touch event!", ev) - --return Event:new("UnkonwnTouchEvent", ev) - --end - --elseif ev.type == EV_SYN then - --if ev.code == SYN_REPORT then - --DEBUG("SYN REPORT") - --elseif ev.code == SYN_MT_REPORT then - --DEBUG("SYN MT_REPORT") - --elseif ev.code == SYN_CONFIG then - --DEBUG("SYN CONFIG") - --else - --DEBUG(ev) - --end else -- some other kind of event that we do not know yet return Event:new("GenericInput", ev) From 5e2537210f304804d7ae0c70606a49803ee6bb33 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sun, 11 Nov 2012 14:29:37 +0800 Subject: [PATCH 05/14] remove comments in menu.lua --- frontend/ui/menu.lua | 6 ------ 1 file changed, 6 deletions(-) diff --git a/frontend/ui/menu.lua b/frontend/ui/menu.lua index 93776dd0b..e3eb15cd6 100644 --- a/frontend/ui/menu.lua +++ b/frontend/ui/menu.lua @@ -85,12 +85,6 @@ function MenuItem:init() GestureRange:new{ ges = "tap", range = self.dimen, - --range = Geom:new{ - --x = self.dimen.x, - --y = self.dimen.y, - --h = self.dimen.h, - --w = self.dimen.w, - --}, }, doc = "Select Menu Item", }, From d4e5b4cb06860342ee115ee18f9a34a5e1c64e4b Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sun, 11 Nov 2012 15:24:11 +0800 Subject: [PATCH 06/14] add readerpaging and readermenu touch support --- frontend/ui/device.lua | 5 ++ frontend/ui/reader/readermenu.lua | 38 ++++++++++--- frontend/ui/reader/readerpaging.lua | 83 +++++++++++++++++++++++------ reader.lua | 2 +- 4 files changed, 106 insertions(+), 22 deletions(-) diff --git a/frontend/ui/device.lua b/frontend/ui/device.lua index 813fc1520..aaa3b1525 100644 --- a/frontend/ui/device.lua +++ b/frontend/ui/device.lua @@ -58,6 +58,11 @@ function Device:isKindle2() end end +function Device:isTouchDevice() + local model = self:getModel() + return (model == "Kindle4") or (model == "KindlePaperWhite") or util.isEmulated() +end + function Device:intoScreenSaver() --os.execute("echo 'screensaver in' >> /mnt/us/event_test.txt") if self.charging_mode == false and self.screen_saver_mode == false then diff --git a/frontend/ui/reader/readermenu.lua b/frontend/ui/reader/readermenu.lua index 0c778cd9a..e61c8280d 100644 --- a/frontend/ui/reader/readermenu.lua +++ b/frontend/ui/reader/readermenu.lua @@ -1,8 +1,25 @@ -ReaderMenu = InputContainer:new{ - key_events = { - ShowMenu = { { "Menu" }, doc = "show menu" }, - }, -} +ReaderMenu = InputContainer:new{} + +function ReaderMenu:init() + if Device:isTouchDevice() then + self.ges_events = { + TapShowMenu = { + GestureRange:new{ + ges = "tap", + range = Geom:new{ + x = 0, y = 0, + w = Screen:getWidth(), + h = Screen:getHeight()/2 + } + } + } + } + else + self.key_events = { + ShowMenu = { { "Menu" }, doc = "show menu" }, + } + end +end function ReaderMenu:onShowMenu() local item_table = {} @@ -49,7 +66,10 @@ function ReaderMenu:onShowMenu() end table.insert(item_table, { - text = "Return to file browser" + text = "Return to file browser", + callback = function() + self.ui:onClose() + end }) local main_menu = Menu:new{ @@ -69,3 +89,9 @@ function ReaderMenu:onShowMenu() return true end + +function ReaderMenu:onTapShowMenu() + self:onShowMenu() + return true +end + diff --git a/frontend/ui/reader/readerpaging.lua b/frontend/ui/reader/readerpaging.lua index 9d93ed606..c41481314 100644 --- a/frontend/ui/reader/readerpaging.lua +++ b/frontend/ui/reader/readerpaging.lua @@ -1,24 +1,65 @@ ReaderPaging = InputContainer:new{ - key_events = { - GotoNextPage = { {Input.group.PgFwd}, doc = "go to next page", event = "GotoPageRel", args = 1 }, - GotoPrevPage = { {Input.group.PgBack}, doc = "go to previous page", event = "GotoPageRel", args = -1 }, - - GotoFirst = { {"1"}, doc = "go to start", event = "GotoPercent", args = 0}, - Goto11 = { {"2"}, doc = "go to 11%", event = "GotoPercent", args = 11}, - Goto22 = { {"3"}, doc = "go to 22%", event = "GotoPercent", args = 22}, - Goto33 = { {"4"}, doc = "go to 33%", event = "GotoPercent", args = 33}, - Goto44 = { {"5"}, doc = "go to 44%", event = "GotoPercent", args = 44}, - Goto55 = { {"6"}, doc = "go to 55%", event = "GotoPercent", args = 55}, - Goto66 = { {"7"}, doc = "go to 66%", event = "GotoPercent", args = 66}, - Goto77 = { {"8"}, doc = "go to 77%", event = "GotoPercent", args = 77}, - Goto88 = { {"9"}, doc = "go to 88%", event = "GotoPercent", args = 88}, - GotoLast = { {"0"}, doc = "go to end", event = "GotoPercent", args = 100}, - }, current_page = 0, number_of_pages = 0 } function ReaderPaging:init() + if Device:isTouchDevice() then + self.ges_events = { + TapForward = { + GestureRange:new{ + ges = "tap", + range = Geom:new{ + x = Screen:getWidth()/2, + y = Screen:getHeight()/2, + w = Screen:getWidth(), + h = Screen:getHeight() + } + } + }, + TapBackward = { + GestureRange:new{ + ges = "tap", + range = Geom:new{ + x = 0, + y = Screen:getHeight()/2, + w = Screen:getWidth()/2, + h = Screen:getHeight()/2, + } + } + } + } + else + self.key_events = { + GotoNextPage = { + {Input.group.PgFwd}, doc = "go to next page", + event = "GotoPageRel", args = 1 }, + GotoPrevPage = { + {Input.group.PgBack}, doc = "go to previous page", + event = "GotoPageRel", args = -1 }, + + GotoFirst = { + {"1"}, doc = "go to start", event = "GotoPercent", args = 0}, + Goto11 = { + {"2"}, doc = "go to 11%", event = "GotoPercent", args = 11}, + Goto22 = { + {"3"}, doc = "go to 22%", event = "GotoPercent", args = 22}, + Goto33 = { + {"4"}, doc = "go to 33%", event = "GotoPercent", args = 33}, + Goto44 = { + {"5"}, doc = "go to 44%", event = "GotoPercent", args = 44}, + Goto55 = { + {"6"}, doc = "go to 55%", event = "GotoPercent", args = 55}, + Goto66 = { + {"7"}, doc = "go to 66%", event = "GotoPercent", args = 66}, + Goto77 = { + {"8"}, doc = "go to 77%", event = "GotoPercent", args = 77}, + Goto88 = { + {"9"}, doc = "go to 88%", event = "GotoPercent", args = 88}, + GotoLast = { + {"0"}, doc = "go to end", event = "GotoPercent", args = 100}, + } + end self.number_of_pages = self.ui.document.info.number_of_pages end @@ -68,3 +109,15 @@ end function ReaderPaging:onCloseDocument() self.ui.doc_settings:saveSetting("last_page", self.current_page) end + +function ReaderPaging:onTapForward() + self:onGotoPageRel(1) + return true +end + +function ReaderPaging:onTapBackward() + self:onGotoPageRel(-1) + return true +end + + diff --git a/reader.lua b/reader.lua index ff465a0e3..51932222c 100755 --- a/reader.lua +++ b/reader.lua @@ -97,7 +97,7 @@ else DEBUG = function() end end -if Device.isKindle4() then +if Device.isKindle4() or Device:isTouchDevice() then -- remove menu item shortcut for K4 Menu.is_enable_shortcut = false end From 1fb10efc43ceaf84433fb15f948c65328f1c89aa Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Tue, 13 Nov 2012 01:12:01 -0500 Subject: [PATCH 07/14] introduce state methods in gesturedetector --- frontend/ui/gesturedetector.lua | 87 ++++++++++++++++++++------------- 1 file changed, 54 insertions(+), 33 deletions(-) diff --git a/frontend/ui/gesturedetector.lua b/frontend/ui/gesturedetector.lua index 3f04902d8..4efc4883e 100644 --- a/frontend/ui/gesturedetector.lua +++ b/frontend/ui/gesturedetector.lua @@ -37,11 +37,6 @@ function GestureRange:match(gs) return false end -GestureDetector = { - ev_stack = {}, - cur_ev = {}, -} - --[[ MT_TRACK_ID: 0 MT_X: 310 @@ -58,21 +53,27 @@ MT_TRACK_ID: -1 SYN REPORT --]] +GestureDetector = { + track_id = {}, + ev_stack = {}, + cur_ev = {}, + state = function(self) + self.state = self.initialState + end, +} + function GestureDetector:feedEvent(ev) if ev.type == EV_SYN then if ev.code == SYN_REPORT then - -- end of one event or release touch? - if self.cur_ev.id == -1 then - -- touch release? - return self:guessGesture() - else - table.insert(self.ev_stack, self.cur_ev) - self.cur_ev = {} - --DEBUG(self.ev_stack) + local re = self.state(self, self.cur_ev) + if re ~= nil then + return re end + self.cur_ev = {} end elseif ev.type == EV_ABS then if ev.code == ABS_MT_SLOT then + self.cur_ev.slot = ev.value elseif ev.code == ABS_MT_TRACKING_ID then self.cur_ev.id = ev.value elseif ev.code == ABS_MT_POSITION_X then @@ -83,28 +84,48 @@ function GestureDetector:feedEvent(ev) end end -function GestureDetector:guessGesture() - local is_recognized = false - local result = {ges = "tap", pos = Geom:new{w=0, h=0}} - - for k,ev in ipairs(self.ev_stack) do - --@TODO do real recognization here (houqp) - is_recognized = true - if ev.x then - result.pos.x = ev.x - end - if ev.y then - result.pos.y = ev.y - end +function GestureDetector:initialState(ev) + if ev.id then + self.track_id[ev.id] = ev.slot end - - if is_recognized then - self.ev_stack = {} - return result - else - DEBUG("Unknown gesture!!", self.ev_stack) - self.ev_stack = {} + -- 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 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 +end +function GestureDetector:clearState() + self.hold_x = nil + self.hold_y = nil + self.state = self.initialState + self.cur_ev = {} +end From f06afb798e7fbf4a071c06fe154af2c8db3723b9 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Thu, 15 Nov 2012 19:58:01 -0500 Subject: [PATCH 08/14] rewrite gesturedetector state machine --- frontend/ui/gesturedetector.lua | 196 ++++++++++++++++++++++++-------- 1 file changed, 150 insertions(+), 46 deletions(-) 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 + + From dfd697453b67b840df8340254e64d1a5ecc3d1f7 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 16 Nov 2012 18:54:01 -0500 Subject: [PATCH 09/14] add time module --- frontend/ui/time.lua | 99 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 frontend/ui/time.lua diff --git a/frontend/ui/time.lua b/frontend/ui/time.lua new file mode 100644 index 000000000..52e47021a --- /dev/null +++ b/frontend/ui/time.lua @@ -0,0 +1,99 @@ +TimeVal = { + sec = 0, + usec = 0, +} + +function TimeVal:new(o) + local o = o or {} + if o.sec == nil then + o.sec = 0 + end + if o.usec == nil then + o.usec = 0 + end + setmetatable(o, self) + self.__index = self + return o +end + + +function TimeVal:__lt(time_b) + if self.sec < time_b.sec then + return true + elseif self.sec > time_b.sec then + return false + else + -- self.sec == time_b.sec + if self.usec < time_b.usec then + return true + else + return false + end + end +end + +function TimeVal:__le(time_b) + if self.sec < time_b.sec then + return true + elseif self.sec > time_b.sec then + return false + else + -- self.sec == time_b.sec + if self.usec > time_b.usec then + return false + else + return true + end + end +end + +function TimeVal:__eq(time_b) + if self.sec == time_b.sec and self.usec == time_b.usec then + return true + else + return false + end +end + +function TimeVal:__sub(time_b) + diff = TimeVal:new{} + + diff.sec = self.sec - time_b.sec + diff.usec = self.usec - time_b.usec + + if diff.sec < 0 and diff.usec > 0 then + diff.sec = diff.sec + 1 + diff.usec = diff.usec - 1000000 + elseif diff.sec > 0 and diff.usec < 0 then + diff.sec = diff.sec - 1 + diff.usec = diff.usec + 1000000 + end + + return diff +end + +function TimeVal:__add(time_b) + sum = TimeVal:new{} + + sum.sec = self.sec + time_b.sec + sum.usec = self.usec + time_b.usec + if sum.usec > 1000000 then + sum.usec = sum.sec - 1000000 + sum.sec = sum.sec + 1 + end + + if sum.sec < 0 and sum.usec > 0 then + sum.sec = sum.sec + 1 + sum.usec = sum.usec - 1000000 + elseif sum.sec > 0 and sum.usec < 0 then + sum.sec = sum.sec - 1 + sum.usec = sum.usec + 1000000 + end + + return sum +end + +function TimeVal:now() + local sec, usec = util.gettime() + return TimeVal:new{sec = sec, usec = usec} +end From a8e4c6ed7375dad55a83885a4fd0d3aaf7c5374b Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 16 Nov 2012 18:55:13 -0500 Subject: [PATCH 10/14] add setTimeOut method in inputevet and use it in gesturedetector --- frontend/ui/gesturedetector.lua | 61 +++++++++++++++++---------------- frontend/ui/inputevent.lua | 39 ++++++++++++++++++--- 2 files changed, 67 insertions(+), 33 deletions(-) diff --git a/frontend/ui/gesturedetector.lua b/frontend/ui/gesturedetector.lua index 15cd8998d..e21d82e4b 100644 --- a/frontend/ui/gesturedetector.lua +++ b/frontend/ui/gesturedetector.lua @@ -1,4 +1,3 @@ -require "ui/inputevent" require "ui/geometry" -- Synchronization events (SYN.code). @@ -51,8 +50,8 @@ SYN REPORT --]] GestureDetector = { - -- all the time parameters are in ms - DOUBLE_TAP_TIME = 500, + -- all the time parameters are in us + DOUBLE_TAP_TIME = 500 * 1000, -- distance parameters DOUBLE_TAP_DISTANCE = 50, PAN_THRESHOLD = 50, @@ -62,21 +61,22 @@ GestureDetector = { cur_ev = {}, ev_start = false, state = function(self, ev) - self.switchState("initialState", ev) + self:switchState("initialState", ev) end, - last_ev_time = nil, + last_ev_timev = nil, -- for tap last_tap = nil, } function GestureDetector:feedEvent(ev) + --DEBUG(ev.type, ev.code, ev.value, ev.time) if ev.type == EV_SYN then if ev.code == SYN_REPORT then - self.cur_ev.time = ev.time + self.cur_ev.timev = TimeVal:new(ev.time) local re = self.state(self, self.cur_ev) - self.last_ev_time = ev.time + self.last_ev_timev = self.cur_ev.timev if re ~= nil then return re end @@ -99,12 +99,11 @@ 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 + local tv_diff = tap2.timev - tap1.timev 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 + (tv_diff.sec == 0 and (tv_diff.usec) < self.DOUBLE_TAP_TIME) ) end @@ -159,22 +158,29 @@ function GestureDetector:tapState(ev) local cur_tap = { x = self.cur_x, y = self.cur_y, - time = ev.time, + timev = ev.timev, } if self.last_tap and self:isDoubleTap(self.last_tap, cur_tap) then ges_ev.ges = "double_tap" self.last_tap = nil + return ges_ev end - if ges_ev.ges == "tap" then - -- set current tap to last tap - self.last_tap = cur_tap - end + -- set current tap to last tap + self.last_tap = cur_tap + Input:setTimeOut(function() + -- double tap will set last_tap to nil + -- so if it is not, then user must only + -- tapped once + if self.last_tap then + self.last_tap = nil + self:clearState() + return ges_ev + end + end, self.cur_ev.timev+TimeVal:new{sec=0, usec=DOUBLE_TAP_TIME}) - self:clearState() - return ges_ev elseif self.state ~= self.tapState then -- switched from other state, probably from initialState -- we return nil in this case @@ -182,21 +188,18 @@ function GestureDetector:tapState(ev) 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 - }) + Input:setTimeOut(function() + if self.state == self.tapState then + -- timer set in tapState, so we switch to hold + return self:switchState("holdState") + end + end, + self.cur_ev.timev + TimeVal:new{sec = 0, usec = 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 (ev.x and math.abs(ev.x - self.cur_x) >= self.PAN_THRESHOLD) or + (ev.y and 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) diff --git a/frontend/ui/inputevent.lua b/frontend/ui/inputevent.lua index dc3cac74a..61a8ab735 100644 --- a/frontend/ui/inputevent.lua +++ b/frontend/ui/inputevent.lua @@ -1,5 +1,6 @@ require "ui/event" require "ui/device" +require "ui/time" require "ui/gesturedetector" require "settings" @@ -224,6 +225,8 @@ Input = { "LPgBack", "RPgBack", "LPgFwd", "RPgFwd" } }, + + timer_callbacks = {}, } function Input:init() @@ -265,18 +268,46 @@ function Input:adjustKindle4EventMap() self.event_map[104] = "LPgFwd" end +function Input:setTimeOut(cb, tv_out) + table.insert(self.timer_callbacks, { + callback = cb, + dead_line = tv_out, + }) +end + function Input:waitEvent(timeout_us, timeout_s) -- wrapper for input.waitForEvents that will retry for some cases local ok, ev + local wait_deadline = TimeVal:now() + TimeVal:new{ + sec = timeout_s, + usec = timeout_us + } while true do - ok, ev = pcall(input.waitForEvent, timeout_us, timeout_s) + if #self.timer_callbacks then + -- we don't block if there is any timer, set wait to 10us + ok, ev = pcall(input.waitForEvent, 10) + else + ok, ev = pcall(input.waitForEvent, timeout_us) + end if ok then break end if ev == "Waiting for input failed: timeout\n" then - -- don't report an error on timeout - ev = nil - break + local tv_now = TimeVal:now() + if #self.timer_thread and tv_now < wait_deadline then + -- check whether timer is up + if tv_now >= timer_thread[1].dead_line then + local ges = self.timer_callbacks[1].callback() + table.remove(self.timer_callbacks, 1) + if ges then + return Event:new("Gesture", ges) + end + end + else + -- don't report an error on timeout + ev = nil + break + end elseif ev == "application forced to quit" then os.exit(0) end From d3e0424122fbd7ef2aa900751c1bbb2cb836f5e0 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sat, 17 Nov 2012 13:41:26 -0500 Subject: [PATCH 11/14] add setTimeOut method in inputevent.lua adapt all the state machine to use setTimeOut. Now supported gestures includes tap, double tap and hold. --- frontend/ui/gesturedetector.lua | 64 +++++++++++++++++++++++++-------- frontend/ui/inputevent.lua | 55 +++++++++++++++++----------- frontend/ui/time.lua | 3 ++ frontend/ui/ui.lua | 1 + input.c | 3 +- 5 files changed, 89 insertions(+), 37 deletions(-) diff --git a/frontend/ui/gesturedetector.lua b/frontend/ui/gesturedetector.lua index e21d82e4b..c405c0236 100644 --- a/frontend/ui/gesturedetector.lua +++ b/frontend/ui/gesturedetector.lua @@ -51,7 +51,8 @@ SYN REPORT GestureDetector = { -- all the time parameters are in us - DOUBLE_TAP_TIME = 500 * 1000, + DOUBLE_TAP_INTERVAL = 300 * 1000, + HOLD_INTERVAL = 1000 * 1000, -- distance parameters DOUBLE_TAP_DISTANCE = 50, PAN_THRESHOLD = 50, @@ -103,13 +104,17 @@ function GestureDetector:isDoubleTap(tap1, tap2) return ( math.abs(tap1.x - tap2.x) < self.DOUBLE_TAP_DISTANCE and math.abs(tap1.y - tap2.y) < self.DOUBLE_TAP_DISTANCE and - (tv_diff.sec == 0 and (tv_diff.usec) < self.DOUBLE_TAP_TIME) + (tv_diff.sec == 0 and (tv_diff.usec) < self.DOUBLE_TAP_INTERVAL) ) end -function GestureDetector:switchState(state, ev) +--[[ +Warning! this method won't update self.state, you need to do it +in each state method! +--]] +function GestureDetector:switchState(state_new, ev) --@TODO do we need to check whether state is valid? (houqp) - return self[state](self, ev) + return self[state_new](self, ev) end function GestureDetector:clearState() @@ -130,7 +135,7 @@ function GestureDetector:initialState(ev) end end if ev.x and ev.y then - -- a new event has just started + -- user starts a new touch motion if not self.ev_start then self.ev_start = true -- default to tap state @@ -141,8 +146,9 @@ end --[[ this method handles both single and double tap -]] +--]] function GestureDetector:tapState(ev) + DEBUG("in tap state...", ev) if ev.id == -1 then -- end of tap event local ges_ev = { @@ -161,8 +167,10 @@ function GestureDetector:tapState(ev) timev = ev.timev, } - if self.last_tap and + if self.last_tap ~= nil and self:isDoubleTap(self.last_tap, cur_tap) then + -- it is a double tap + self:clearState() ges_ev.ges = "double_tap" self.last_tap = nil return ges_ev @@ -170,31 +178,42 @@ function GestureDetector:tapState(ev) -- set current tap to last tap self.last_tap = cur_tap + + local dead_line = self.cur_ev.timev + TimeVal:new{ + sec = 0, usec = self.DOUBLE_TAP_INTERVAL, + } + DEBUG("set up tap timer") Input:setTimeOut(function() + print("in tap timer", self.last_tap ~= nil) -- double tap will set last_tap to nil -- so if it is not, then user must only -- tapped once - if self.last_tap then + if self.last_tap ~= nil then self.last_tap = nil - self:clearState() + -- we are using closure here return ges_ev end - end, self.cur_ev.timev+TimeVal:new{sec=0, usec=DOUBLE_TAP_TIME}) - + end, dead_line) + -- we are already at the end of touch event + -- so reset the state + self:clearState() 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) + DEBUG("set up hold timer") + local dead_line = self.cur_ev.timev + TimeVal:new{ + sec = 0, usec = self.HOLD_INTERVAL + } Input:setTimeOut(function() + print("hold timer", self.state == self.tapState) if self.state == self.tapState then -- timer set in tapState, so we switch to hold return self:switchState("holdState") end - end, - self.cur_ev.timev + TimeVal:new{sec = 0, usec = HOLD_TIME}) + end, dead_line) else -- it is not end of touch event, see if we need to switch to -- other states @@ -208,8 +227,10 @@ function GestureDetector:tapState(ev) end function GestureDetector:panState(ev) + DEBUG("in pan state...") if ev.id == -1 then -- end of pan, signal swipe gesture + self:clearState() elseif self.state ~= self.panState then self.state = self.panState --@TODO calculate direction here (houqp) @@ -220,7 +241,11 @@ function GestureDetector:panState(ev) end function GestureDetector:holdState(ev) + DEBUG("in hold state...") + -- when we switch to hold state, we pass no ev + -- so ev = nil if not ev and self.cur_x and self.cur_y then + self.state = self.holdState return { ges = "hold", pos = Geom:new{ @@ -231,7 +256,16 @@ function GestureDetector:holdState(ev) } end if ev.id == -1 then - -- end of hold, signal hold release? + -- end of hold, signal hold release + self:clearState() + return { + ges = "hold_release", + pos = Geom:new{ + x = self.cur_x, + y = self.cur_y, + w = 0, h = 0, + } + } end end diff --git a/frontend/ui/inputevent.lua b/frontend/ui/inputevent.lua index 61a8ab735..5531fbca0 100644 --- a/frontend/ui/inputevent.lua +++ b/frontend/ui/inputevent.lua @@ -269,10 +269,19 @@ function Input:adjustKindle4EventMap() end function Input:setTimeOut(cb, tv_out) - table.insert(self.timer_callbacks, { + local item = { callback = cb, dead_line = tv_out, - }) + } + for k,v in ipairs(self.timer_callbacks) do + if v.dead_line > tv_out then + table.insert(self.timer_callbacks, k, item) + break + end + end + if #self.timer_callbacks <= 0 then + self.timer_callbacks[1] = item + end end function Input:waitEvent(timeout_us, timeout_s) @@ -283,9 +292,26 @@ function Input:waitEvent(timeout_us, timeout_s) usec = timeout_us } while true do - if #self.timer_callbacks then + if #self.timer_callbacks > 0 then -- we don't block if there is any timer, set wait to 10us - ok, ev = pcall(input.waitForEvent, 10) + while #self.timer_callbacks > 0 do + ok, ev = pcall(input.waitForEvent, 100) + if ok then break end + local tv_now = TimeVal:now() + if ((not timeout_us and not timeout_s) or tv_now < wait_deadline) then + -- check whether timer is up + if tv_now >= self.timer_callbacks[1].dead_line then + local ges = self.timer_callbacks[1].callback() + table.remove(self.timer_callbacks, 1) + if ges then + -- Do we really need to clear all setTimeOut after + -- decided a gesture? FIXME + Input.timer_callbacks = {} + return Event:new("Gesture", ges) + end + end + end + end else ok, ev = pcall(input.waitForEvent, timeout_us) end @@ -293,25 +319,13 @@ function Input:waitEvent(timeout_us, timeout_s) break end if ev == "Waiting for input failed: timeout\n" then - local tv_now = TimeVal:now() - if #self.timer_thread and tv_now < wait_deadline then - -- check whether timer is up - if tv_now >= timer_thread[1].dead_line then - local ges = self.timer_callbacks[1].callback() - table.remove(self.timer_callbacks, 1) - if ges then - return Event:new("Gesture", ges) - end - end - else - -- don't report an error on timeout - ev = nil - break - end + -- don't report an error on timeout + ev = nil + break elseif ev == "application forced to quit" then os.exit(0) end - DEBUG("got error waiting for events:", ev) + --DEBUG("got error waiting for events:", ev) if ev ~= "Waiting for input failed: 4\n" then -- we only abort if the error is not EINTR break @@ -363,7 +377,6 @@ function Input:waitEvent(timeout_us, timeout_s) end elseif ev.type == EV_ABS or ev.type == EV_SYN then local touch_ges = GestureDetector:feedEvent(ev) - DEBUG(touch_ges) if touch_ges then return Event:new("Gesture", touch_ges) end diff --git a/frontend/ui/time.lua b/frontend/ui/time.lua index 52e47021a..9189cc614 100644 --- a/frontend/ui/time.lua +++ b/frontend/ui/time.lua @@ -10,6 +10,9 @@ function TimeVal:new(o) end if o.usec == nil then o.usec = 0 + elseif o.usec > 1000000 then + o.sec = o.sec + maht.floor(o.usec/1000000) + o.usec = o.usec % 1000000 end setmetatable(o, self) self.__index = self diff --git a/frontend/ui/ui.lua b/frontend/ui/ui.lua index be876c099..23b14eaa2 100644 --- a/frontend/ui/ui.lua +++ b/frontend/ui/ui.lua @@ -200,6 +200,7 @@ function UIManager:run() -- delegate input_event to handler if input_event then + DEBUG(input_event) self:sendEvent(input_event) end end diff --git a/input.c b/input.c index 245abd483..baa77d7a3 100644 --- a/input.c +++ b/input.c @@ -299,8 +299,9 @@ static int waitForInput(lua_State *L) { } int ticks = SDL_GetTicks(); - if (usecs < 0) + if (usecs < 0) { SDL_WaitEvent(&event); + } else { while (SDL_GetTicks()-ticks <= usecs/1000) { if (SDL_PollEvent(&event)) break; From 324f67412aa060000c8f70538c43677c11e712d3 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sat, 17 Nov 2012 13:51:34 -0500 Subject: [PATCH 12/14] fix bug in timer_callbacks scheduling & change variable name watiEvent should stop checking timer_callbacks when execceds wait timeout set by timeout_us. --- frontend/ui/gesturedetector.lua | 12 ++++++------ frontend/ui/inputevent.lua | 18 ++++++++++-------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/frontend/ui/gesturedetector.lua b/frontend/ui/gesturedetector.lua index c405c0236..84a77d662 100644 --- a/frontend/ui/gesturedetector.lua +++ b/frontend/ui/gesturedetector.lua @@ -179,12 +179,12 @@ function GestureDetector:tapState(ev) -- set current tap to last tap self.last_tap = cur_tap - local dead_line = self.cur_ev.timev + TimeVal:new{ + DEBUG("set up tap timer") + local deadline = self.cur_ev.timev + TimeVal:new{ sec = 0, usec = self.DOUBLE_TAP_INTERVAL, } - DEBUG("set up tap timer") Input:setTimeOut(function() - print("in tap timer", self.last_tap ~= nil) + DEBUG("in tap timer", self.last_tap ~= nil) -- double tap will set last_tap to nil -- so if it is not, then user must only -- tapped once @@ -193,7 +193,7 @@ function GestureDetector:tapState(ev) -- we are using closure here return ges_ev end - end, dead_line) + end, deadline) -- we are already at the end of touch event -- so reset the state self:clearState() @@ -204,7 +204,7 @@ function GestureDetector:tapState(ev) self.cur_x = ev.x self.cur_y = ev.y DEBUG("set up hold timer") - local dead_line = self.cur_ev.timev + TimeVal:new{ + local deadline = self.cur_ev.timev + TimeVal:new{ sec = 0, usec = self.HOLD_INTERVAL } Input:setTimeOut(function() @@ -213,7 +213,7 @@ function GestureDetector:tapState(ev) -- timer set in tapState, so we switch to hold return self:switchState("holdState") end - end, dead_line) + end, deadline) else -- it is not end of touch event, see if we need to switch to -- other states diff --git a/frontend/ui/inputevent.lua b/frontend/ui/inputevent.lua index 5531fbca0..81a326513 100644 --- a/frontend/ui/inputevent.lua +++ b/frontend/ui/inputevent.lua @@ -271,10 +271,10 @@ end function Input:setTimeOut(cb, tv_out) local item = { callback = cb, - dead_line = tv_out, + deadline = tv_out, } for k,v in ipairs(self.timer_callbacks) do - if v.dead_line > tv_out then + if v.deadline > tv_out then table.insert(self.timer_callbacks, k, item) break end @@ -300,7 +300,7 @@ function Input:waitEvent(timeout_us, timeout_s) local tv_now = TimeVal:now() if ((not timeout_us and not timeout_s) or tv_now < wait_deadline) then -- check whether timer is up - if tv_now >= self.timer_callbacks[1].dead_line then + if tv_now >= self.timer_callbacks[1].deadline then local ges = self.timer_callbacks[1].callback() table.remove(self.timer_callbacks, 1) if ges then @@ -308,13 +308,15 @@ function Input:waitEvent(timeout_us, timeout_s) -- decided a gesture? FIXME Input.timer_callbacks = {} return Event:new("Gesture", ges) - end - end - end - end + end -- EOF if ges + end -- EOF if deadline reached + else + break + end -- EOF if not exceed wait timeout + end -- while #timer_callbacks > 0 else ok, ev = pcall(input.waitForEvent, timeout_us) - end + end -- EOF if #timer_callbacks > 0 if ok then break end From a7b2215249817f725e7fba81b3a0625b754bee0f Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 23 Nov 2012 01:04:56 -0500 Subject: [PATCH 13/14] bug fix in time.lua a crazy typo --- frontend/ui/gesturedetector.lua | 5 ++--- frontend/ui/time.lua | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/frontend/ui/gesturedetector.lua b/frontend/ui/gesturedetector.lua index 84a77d662..449e191c5 100644 --- a/frontend/ui/gesturedetector.lua +++ b/frontend/ui/gesturedetector.lua @@ -185,9 +185,8 @@ function GestureDetector:tapState(ev) } Input:setTimeOut(function() DEBUG("in tap timer", self.last_tap ~= nil) - -- double tap will set last_tap to nil - -- so if it is not, then user must only - -- tapped once + -- double tap will set last_tap to nil so if it is not, then + -- user must only tapped once if self.last_tap ~= nil then self.last_tap = nil -- we are using closure here diff --git a/frontend/ui/time.lua b/frontend/ui/time.lua index 9189cc614..90f8eb84d 100644 --- a/frontend/ui/time.lua +++ b/frontend/ui/time.lua @@ -81,7 +81,7 @@ function TimeVal:__add(time_b) sum.sec = self.sec + time_b.sec sum.usec = self.usec + time_b.usec if sum.usec > 1000000 then - sum.usec = sum.sec - 1000000 + sum.usec = sum.usec - 1000000 sum.sec = sum.sec + 1 end From c701248a8df27bed05c399b67e8f3a2f1445ac00 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sun, 25 Nov 2012 15:57:29 +0800 Subject: [PATCH 14/14] rename Input:setTimeOut to Input:setTimtout To keep consistent with Javascript setTimeout function... --- frontend/ui/gesturedetector.lua | 4 ++-- frontend/ui/inputevent.lua | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/ui/gesturedetector.lua b/frontend/ui/gesturedetector.lua index 449e191c5..9d1fc01bb 100644 --- a/frontend/ui/gesturedetector.lua +++ b/frontend/ui/gesturedetector.lua @@ -183,7 +183,7 @@ function GestureDetector:tapState(ev) local deadline = self.cur_ev.timev + TimeVal:new{ sec = 0, usec = self.DOUBLE_TAP_INTERVAL, } - Input:setTimeOut(function() + Input:setTimeout(function() DEBUG("in tap timer", self.last_tap ~= nil) -- double tap will set last_tap to nil so if it is not, then -- user must only tapped once @@ -206,7 +206,7 @@ function GestureDetector:tapState(ev) local deadline = self.cur_ev.timev + TimeVal:new{ sec = 0, usec = self.HOLD_INTERVAL } - Input:setTimeOut(function() + Input:setTimeout(function() print("hold timer", self.state == self.tapState) if self.state == self.tapState then -- timer set in tapState, so we switch to hold diff --git a/frontend/ui/inputevent.lua b/frontend/ui/inputevent.lua index 81a326513..89a496f29 100644 --- a/frontend/ui/inputevent.lua +++ b/frontend/ui/inputevent.lua @@ -268,7 +268,7 @@ function Input:adjustKindle4EventMap() self.event_map[104] = "LPgFwd" end -function Input:setTimeOut(cb, tv_out) +function Input:setTimeout(cb, tv_out) local item = { callback = cb, deadline = tv_out, @@ -304,7 +304,7 @@ function Input:waitEvent(timeout_us, timeout_s) local ges = self.timer_callbacks[1].callback() table.remove(self.timer_callbacks, 1) if ges then - -- Do we really need to clear all setTimeOut after + -- Do we really need to clear all setTimeout after -- decided a gesture? FIXME Input.timer_callbacks = {} return Event:new("Gesture", ges)