From f5b89744c55c2a227cceb4bcfbefb3980b723c64 Mon Sep 17 00:00:00 2001 From: chrox Date: Wed, 6 Mar 2013 20:59:48 +0800 Subject: [PATCH 1/7] feed evs in all slots to state machine --- frontend/ui/inputevent.lua | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/frontend/ui/inputevent.lua b/frontend/ui/inputevent.lua index c7426a789..dd19d738b 100644 --- a/frontend/ui/inputevent.lua +++ b/frontend/ui/inputevent.lua @@ -26,8 +26,6 @@ ABS_MT_POSITION_Y = 54 ABS_MT_TRACKING_ID = 57 ABS_MT_PRESSURE = 58 - - --[[ an interface for key presses ]] @@ -241,6 +239,7 @@ end function Input:initTouchState() self.cur_slot = 0 + self.MTSlots = {} self.ev_slots = { [0] = { slot = 0, @@ -437,10 +436,12 @@ attribute of the current slot. function Input:handleTouchEv(ev) if ev.type == EV_SYN then if ev.code == SYN_REPORT then - self:setCurrentMtSlot("timev", TimeVal:new(ev.time)) - -- send ev to state machine - local touch_ges = GestureDetector:feedEvent( - self:getCurrentMtSlot()) + for _, MTSlot in pairs(self.MTSlots) do + self:setMtSlot(MTSlot.slot, "timev", TimeVal:new(ev.time)) + end + -- feed ev in all slots to state machine + local touch_ges = GestureDetector:feedEvent(self.MTSlots) + self.MTSlots = {} if touch_ges then return Event:new("Gesture", GestureDetector:adjustGesCoordinate(touch_ges) @@ -448,7 +449,13 @@ function Input:handleTouchEv(ev) end end elseif ev.type == EV_ABS then + if #self.MTSlots == 0 then + table.insert(self.MTSlots, self:getMtSlot(self.cur_slot)) + end if ev.code == ABS_MT_SLOT then + if self.cur_slot ~= ev.value then + table.insert(self.MTSlots, self:getMtSlot(ev.value)) + end self.cur_slot = ev.value elseif ev.code == ABS_MT_TRACKING_ID then self:setCurrentMtSlot("id", ev.value) From c3d7d2df47ebac58e38ebed6353b5f066b08e23f Mon Sep 17 00:00:00 2001 From: chrox Date: Wed, 6 Mar 2013 21:04:33 +0800 Subject: [PATCH 2/7] add two-finger tap gesture --- frontend/ui/gesturedetector.lua | 223 +++++++++++++++++++++----------- 1 file changed, 148 insertions(+), 75 deletions(-) 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" From 64addcc8418b8907ff2a5a8679d5f1e528e18372 Mon Sep 17 00:00:00 2001 From: chrox Date: Wed, 6 Mar 2013 21:31:41 +0800 Subject: [PATCH 3/7] bugfix: fix increment of refresh count If full refresh is false and refresh count is FULL_REFRESH_COUNT - 1, every refresh becomes full refresh. This patch increases refresh count when a full refresh is performed no matter whether full refresh is requested. --- frontend/ui/ui.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/ui/ui.lua b/frontend/ui/ui.lua index 7a77284f3..8cecea127 100644 --- a/frontend/ui/ui.lua +++ b/frontend/ui/ui.lua @@ -188,10 +188,11 @@ function UIManager:run() end -- refresh FB Screen:refresh(self.refresh_type) -- TODO: refresh explicitly only repainted area + -- increase refresh_count only when full refresh is requested or performed + local refresh_increment = (full_refresh or self.refresh_type == 0) and 1 or 0 + self.refresh_count = (self.refresh_count + refresh_increment)%self.FULL_REFRESH_COUNT -- reset refresh_type self.refresh_type = 1 - -- increase refresh_count only when full refresh is requested - self.refresh_count = (self.refresh_count + (full_refresh and 1 or 0))%self.FULL_REFRESH_COUNT end self:checkTasks() From d879603352a9eca03f01438719b35c05d24f7c02 Mon Sep 17 00:00:00 2001 From: chrox Date: Wed, 6 Mar 2013 22:24:48 +0800 Subject: [PATCH 4/7] set widget dirty with "auto", "full" and "partial" arguments The "auto" argument requests a full refresh and increses full refresh count by one. And the "full" argument forces a full refresh and reset full refresh count. And the "partial" argument requests a partial refresh. --- frontend/ui/ui.lua | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/frontend/ui/ui.lua b/frontend/ui/ui.lua index 8cecea127..8a552ef7e 100644 --- a/frontend/ui/ui.lua +++ b/frontend/ui/ui.lua @@ -78,12 +78,11 @@ end -- register a widget to be repainted function UIManager:setDirty(widget, refresh_type) + -- "auto": request full refresh + -- "full": force full refresh + -- "partial": partial refresh if not refresh_type then - refresh_type = "full" - elseif refresh_type == 0 then - refresh_type = "full" - elseif refresh_type == 1 then - refresh_type = "partial" + refresh_type = "auto" end self._dirty[widget] = refresh_type end @@ -165,13 +164,17 @@ function UIManager:run() -- repaint dirty widgets local dirty = false - local full_refresh = false + local request_full_refresh = false + local force_full_refresh = false for _, widget in ipairs(self._window_stack) do if self.repaint_all or self._dirty[widget.widget] then widget.widget:paintTo(Screen.bb, widget.x, widget.y) - if self._dirty[widget.widget] == "full" then - full_refresh = true + if self._dirty[widget.widget] == "auto" then + request_full_refresh = true end + if self._dirty[widget.widget] == "full" then + force_full_refresh = true + end -- and remove from list after painting self._dirty[widget.widget] = nil -- trigger repaint @@ -181,6 +184,9 @@ function UIManager:run() self.repaint_all = false if dirty then + if force_full_refresh then + self.refresh_count = self.FULL_REFRESH_COUNT - 1 + end if self.refresh_count == self.FULL_REFRESH_COUNT - 1 then self.refresh_type = 0 else @@ -189,7 +195,7 @@ function UIManager:run() -- refresh FB Screen:refresh(self.refresh_type) -- TODO: refresh explicitly only repainted area -- increase refresh_count only when full refresh is requested or performed - local refresh_increment = (full_refresh or self.refresh_type == 0) and 1 or 0 + local refresh_increment = (request_full_refresh or self.refresh_type == 0) and 1 or 0 self.refresh_count = (self.refresh_count + refresh_increment)%self.FULL_REFRESH_COUNT -- reset refresh_type self.refresh_type = 1 From 5c2d9bb1ad2edfaa6db4198735a99b0a189356c2 Mon Sep 17 00:00:00 2001 From: chrox Date: Wed, 6 Mar 2013 22:36:51 +0800 Subject: [PATCH 5/7] screenshot with finger touch at the two corners of the diagonal --- frontend/ui/gesturedetector.lua | 8 ++++---- frontend/ui/reader/readerscreenshot.lua | 25 +++++++++++++++++++++++++ frontend/ui/readerui.lua | 8 ++++++++ 3 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 frontend/ui/reader/readerscreenshot.lua diff --git a/frontend/ui/gesturedetector.lua b/frontend/ui/gesturedetector.lua index 68550623d..1ab5bf6f4 100644 --- a/frontend/ui/gesturedetector.lua +++ b/frontend/ui/gesturedetector.lua @@ -7,7 +7,7 @@ GestureRange = { -- temproal range limits the gesture emitting rate rate = nil, -- span limits of this gesture - span = nil, + scale = nil, } function GestureRange:new(o) @@ -34,8 +34,8 @@ function GestureRange:match(gs) return false end end - if self.span then - if self.span[1] > gs.span or self.span[2] < gs.span then + if self.scale then + if self.scale[1] > gs.span or self.scale[2] < gs.span then return false end end @@ -244,7 +244,7 @@ function GestureDetector:tapState(tev) span = pos0:distance(pos1), time = tev.timev, } - DEBUG("two-finger tap detected") + DEBUG("two-finger tap detected with span", pos0:distance(pos1)) self:clearState(0) self:clearState(1) return ges_ev diff --git a/frontend/ui/reader/readerscreenshot.lua b/frontend/ui/reader/readerscreenshot.lua new file mode 100644 index 000000000..838e8f2c5 --- /dev/null +++ b/frontend/ui/reader/readerscreenshot.lua @@ -0,0 +1,25 @@ + +ReaderScreenshot = InputContainer:new{} + +function ReaderScreenshot:init() + local diagonal = math.sqrt( + math.pow(Screen:getWidth(), 2) + + math.pow(Screen:getHeight(), 2) + ) + self.ges_events = { + Screenshot = { + GestureRange:new{ + ges = "two_finger_tap", + scale = {diagonal - 80*Screen:getDPI()/167, diagonal}, + rate = 1.0, + } + }, + } +end + +function ReaderScreenshot:onScreenshot() + os.execute("screenshot") + UIManager:setDirty(self.view.dialog, "full") + return true +end + diff --git a/frontend/ui/readerui.lua b/frontend/ui/readerui.lua index 8e2c5808f..b180731c8 100644 --- a/frontend/ui/readerui.lua +++ b/frontend/ui/readerui.lua @@ -14,6 +14,7 @@ require "ui/reader/readerconfig" require "ui/reader/readercropping" require "ui/reader/readerkopt" require "ui/reader/readercopt" +require "ui/reader/readerscreenshot" --[[ This is an abstraction for a reader interface @@ -90,6 +91,13 @@ function ReaderUI:init() ui = self } table.insert(self, reader_bm) + -- screenshot controller + local reader_ss = ReaderScreenshot:new{ + dialog = self.dialog, + view = self[1], + ui = self + } + table.insert(self, reader_ss) if self.document.info.has_pages then -- for page specific controller From 71c4f2dfc53b93ae9420c051920493bed60f03ee Mon Sep 17 00:00:00 2001 From: chrox Date: Thu, 7 Mar 2013 18:05:22 +0800 Subject: [PATCH 6/7] add active widgets in window stack that will always handle events --- frontend/ui/ui.lua | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/frontend/ui/ui.lua b/frontend/ui/ui.lua index 8a552ef7e..76b353967 100644 --- a/frontend/ui/ui.lua +++ b/frontend/ui/ui.lua @@ -19,6 +19,9 @@ UIManager = { -- force to repaint all the widget is stack, will be reset to false -- after each ui loop repaint_all = false, + -- force to do full refresh, will be reset to false + -- after each ui loop + full_refresh = false, -- trigger a full refresh when counter reaches FULL_REFRESH_COUNT FULL_REFRESH_COUNT = 6, refresh_count = 0, @@ -95,15 +98,19 @@ end -- transmit an event to registered widgets function UIManager:sendEvent(event) -- top level widget has first access to the event - local consumed = self._window_stack[#self._window_stack].widget:handleEvent(event) + if self._window_stack[#self._window_stack].widget:handleEvent(event) then + return + end - -- if the event is not consumed, always-active widgets can access it + -- if the event is not consumed, active widgets can access it for _, widget in ipairs(self._window_stack) do - if consumed then - break - end if widget.widget.is_always_active then - consumed = widget.widget:handleEvent(event) + if widget.widget:handleEvent(event) then return end + end + if widget.widget.active_widgets then + for _, active_widget in ipairs(widget.widget.active_widgets) do + if active_widget:handleEvent(event) then return end + end end end end @@ -181,7 +188,14 @@ function UIManager:run() dirty = true end end + + if self.full_refresh then + dirty = true + force_full_refresh = true + end + self.repaint_all = false + self.full_refresh = false if dirty then if force_full_refresh then From 7fb3b02117949c283c33d216609bcf307daf443e Mon Sep 17 00:00:00 2001 From: chrox Date: Thu, 7 Mar 2013 18:06:18 +0800 Subject: [PATCH 7/7] make reader screenshot widget always active --- frontend/ui/reader/readerscreenshot.lua | 2 +- frontend/ui/readerui.lua | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/ui/reader/readerscreenshot.lua b/frontend/ui/reader/readerscreenshot.lua index 838e8f2c5..0c438abab 100644 --- a/frontend/ui/reader/readerscreenshot.lua +++ b/frontend/ui/reader/readerscreenshot.lua @@ -19,7 +19,7 @@ end function ReaderScreenshot:onScreenshot() os.execute("screenshot") - UIManager:setDirty(self.view.dialog, "full") + UIManager.full_refresh = true return true end diff --git a/frontend/ui/readerui.lua b/frontend/ui/readerui.lua index b180731c8..ea69f128e 100644 --- a/frontend/ui/readerui.lua +++ b/frontend/ui/readerui.lua @@ -26,6 +26,7 @@ ReaderUI = InputContainer:new{ key_events = { Close = { {"Home"}, doc = "close document", event = "Close" }, }, + active_widgets = {}, -- our own size dimen = Geom:new{ w = 400, h = 600 }, @@ -97,7 +98,7 @@ function ReaderUI:init() view = self[1], ui = self } - table.insert(self, reader_ss) + table.insert(self.active_widgets, reader_ss) if self.document.info.has_pages then -- for page specific controller