diff --git a/frontend/device/gesturedetector.lua b/frontend/device/gesturedetector.lua index e9339fb12..c31eb76c9 100644 --- a/frontend/device/gesturedetector.lua +++ b/frontend/device/gesturedetector.lua @@ -47,22 +47,31 @@ local TimeVal = require("ui/timeval") local logger = require("logger") local util = require("util") --- all the time parameters are in us -local ges_double_tap_interval = G_reader_settings:readSetting("ges_double_tap_interval") or 300 * 1000 -local ges_two_finger_tap_duration = G_reader_settings:readSetting("ges_two_finger_tap_duration") or 300 * 1000 -local ges_hold_interval = G_reader_settings:readSetting("ges_hold_interval") or 500 * 1000 -local ges_pan_delayed_interval = G_reader_settings:readSetting("ges_pan_delayed_interval") or 500 * 1000 -local ges_swipe_interval = G_reader_settings:readSetting("ges_swipe_interval") or 900 * 1000 +-- default values (all the time parameters are in microseconds) +local TAP_INTERVAL = 0 * 1000 +local DOUBLE_TAP_INTERVAL = 300 * 1000 +local TWO_FINGER_TAP_DURATION = 300 * 1000 +local HOLD_INTERVAL = 500 * 1000 +local PAN_DELAYED_INTERVAL = 500 * 1000 +local SWIPE_INTERVAL = 900 * 1000 +-- current values +local ges_tap_interval = G_reader_settings:readSetting("ges_tap_interval") or TAP_INTERVAL +local ges_double_tap_interval = G_reader_settings:readSetting("ges_double_tap_interval") or DOUBLE_TAP_INTERVAL +local ges_two_finger_tap_duration = G_reader_settings:readSetting("ges_two_finger_tap_duration") or TWO_FINGER_TAP_DURATION +local ges_hold_interval = G_reader_settings:readSetting("ges_hold_interval") or HOLD_INTERVAL +local ges_pan_delayed_interval = G_reader_settings:readSetting("ges_pan_delayed_interval") or PAN_DELAYED_INTERVAL +local ges_swipe_interval = G_reader_settings:readSetting("ges_swipe_interval") or SWIPE_INTERVAL local GestureDetector = { -- must be initialized with the Input singleton class input = nil, - -- default values (all the time parameters are in us) - DOUBLE_TAP_INTERVAL = 300 * 1000, - TWO_FINGER_TAP_DURATION = 300 * 1000, - HOLD_INTERVAL = 500 * 1000, - PAN_DELAYED_INTERVAL = 500 * 1000, - SWIPE_INTERVAL = 900 * 1000, + -- default values (accessed for display by plugins/gestures.koplugin) + TAP_INTERVAL = TAP_INTERVAL, + DOUBLE_TAP_INTERVAL = DOUBLE_TAP_INTERVAL, + TWO_FINGER_TAP_DURATION = TWO_FINGER_TAP_DURATION, + HOLD_INTERVAL = HOLD_INTERVAL, + PAN_DELAYED_INTERVAL = PAN_DELAYED_INTERVAL, + SWIPE_INTERVAL = SWIPE_INTERVAL, -- pinch/spread direction table DIRECTION_TABLE = { east = "horizontal", @@ -103,6 +112,7 @@ function GestureDetector:init() -- distance parameters self.TWO_FINGER_TAP_REGION = 20 * scaler self.DOUBLE_TAP_DISTANCE = 50 * scaler + self.SINGLE_TAP_BOUNCE_DISTANCE = self.DOUBLE_TAP_DISTANCE self.PAN_THRESHOLD = self.DOUBLE_TAP_DISTANCE self.MULTISWIPE_THRESHOLD = self.DOUBLE_TAP_DISTANCE end @@ -144,6 +154,15 @@ end --[[ tap2 is the later tap --]] +function GestureDetector:isTapBounce(tap1, tap2) + local tv_diff = tap2.timev - tap1.timev + return ( + math.abs(tap1.x - tap2.x) < self.SINGLE_TAP_BOUNCE_DISTANCE and + math.abs(tap1.y - tap2.y) < self.SINGLE_TAP_BOUNCE_DISTANCE and + (tv_diff.sec == 0 and (tv_diff.usec) < ges_tap_interval) + ) +end + function GestureDetector:isDoubleTap(tap1, tap2) local tv_diff = tap2.timev - tap1.timev return ( @@ -244,7 +263,9 @@ function GestureDetector:clearState(slot) end function GestureDetector:setNewInterval(type, interval) - if type == "ges_double_tap_interval" then + if type == "ges_tap_interval" then + ges_tap_interval = interval + elseif type == "ges_double_tap_interval" then ges_double_tap_interval = interval elseif type == "ges_two_finger_tap_duration" then ges_two_finger_tap_duration = interval @@ -258,7 +279,9 @@ function GestureDetector:setNewInterval(type, interval) end function GestureDetector:getInterval(type) - if type == "ges_double_tap_interval" then + if type == "ges_tap_interval" then + return ges_tap_interval + elseif type == "ges_double_tap_interval" then return ges_double_tap_interval elseif type == "ges_two_finger_tap_duration" then return ges_two_finger_tap_duration @@ -330,6 +353,8 @@ function GestureDetector:tapState(tev) self:clearState(slot) end elseif self.last_tevs[slot] ~= nil then + -- Normal single tap seems to always go thru here + -- (the next 'else' might be there for edge cases) return self:handleDoubleTap(tev) else -- last tev in this slot is cleared by last two finger tap @@ -368,6 +393,17 @@ function GestureDetector:handleDoubleTap(tev) timev = tev.timev, } + -- We do tap bounce detection even when double tap is enabled (so, double tap + -- is triggered when: ges_tap_interval <= delay < ges_double_tap_interval) + if ges_tap_interval > 0 and self.last_taps[slot] ~= nil and self:isTapBounce(self.last_taps[slot], cur_tap) then + logger.dbg("tap bounce detected in slot", slot, ": ignored") + -- Simply ignore it, and clear state as this is the end of a touch event + -- (this doesn't clear self.last_taps[slot], so a 3rd tap can be detected + -- as a double tap) + self:clearState(slot) + return + end + if not self.input.disable_double_tap and self.last_taps[slot] ~= nil and self:isDoubleTap(self.last_taps[slot], cur_tap) then -- it is a double tap @@ -381,16 +417,27 @@ function GestureDetector:handleDoubleTap(tev) -- set current tap to last tap self.last_taps[slot] = cur_tap - logger.dbg("set up tap timer") + if self.input.disable_double_tap then + -- We can send the event immediately (no need for the + -- timer stuff needed for double tap support) + logger.dbg("single tap detected in slot", slot, ges_ev.pos) + self:clearState(slot) + return ges_ev + end + + -- Double tap enabled: we can't send this single tap immediately as it + -- may be the start of a double tap. We'll send it as a single tap after + -- a timer if no second tap happened in the double tap delay. + logger.dbg("set up single/double tap timer") -- deadline should be calculated by adding current tap time and the interval local deadline = cur_tap.timev + TimeVal:new{ sec = 0, usec = not self.input.disable_double_tap and ges_double_tap_interval or 0, } self.input:setTimeout(function() - logger.dbg("in tap timer", self.last_taps[slot] ~= nil) + logger.dbg("in single/double 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 + -- user has not double-tap'ed: it's a single tap if self.last_taps[slot] ~= nil then self.last_taps[slot] = nil -- we are using closure here diff --git a/plugins/gestures.koplugin/main.lua b/plugins/gestures.koplugin/main.lua index 099d0a50e..60df9df2a 100644 --- a/plugins/gestures.koplugin/main.lua +++ b/plugins/gestures.koplugin/main.lua @@ -437,6 +437,7 @@ function Gestures:addIntervals(menu_items) sub_item_table = { { text = _("Text selection rate"), + keep_menu_open = true, callback = function() local SpinWidget = require("ui/widget/spinwidget") local current_value = G_reader_settings:readSetting("hold_pan_rate") @@ -444,18 +445,19 @@ function Gestures:addIntervals(menu_items) current_value = Screen.low_pan_rate and 5.0 or 30.0 end local items = SpinWidget:new{ - text = T(_([[ + title_text = _("Text selection rate"), + info_text = T(_([[ The rate is how often screen will be refreshed per second while selecting text. Higher values mean faster screen updates, but also use more CPU. + Default value: %1]]), Screen.low_pan_rate and 5.0 or 30.0), - width = math.floor(Screen:getWidth() * 0.6), + width = math.floor(Screen:getWidth() * 0.75), value = current_value, value_min = 1.0, value_max = 60.0, value_step = 1, value_hold_step = 15, ok_text = _("Set rate"), - title_text = _("Text selection rate"), default_value = Screen.low_pan_rate and 5.0 or 30.0, callback = function(spin) G_reader_settings:saveSetting("hold_pan_rate", spin.value) @@ -466,24 +468,55 @@ Default value: %1]]), Screen.low_pan_rate and 5.0 or 30.0), end, separator = true, }, + { + text = _("Tap interval"), + keep_menu_open = true, + callback = function() + local SpinWidget = require("ui/widget/spinwidget") + local GestureDetector = require("device/gesturedetector") + local items = SpinWidget:new{ + title_text = _("Tap interval"), + info_text = T(_([[ +Any other taps made within this interval after a first tap will be considered accidental and ignored. + +The interval value is in milliseconds and can range from 0 (0 second) to 2000 (2 seconds). +Default value: %1]]), GestureDetector.TAP_INTERVAL/1000), + width = math.floor(Screen:getWidth() * 0.75), + value = GestureDetector:getInterval("ges_tap_interval")/1000, + value_min = 0, + value_max = 2000, + value_step = 50, + value_hold_step = 200, + ok_text = _("Set interval"), + default_value = GestureDetector.TAP_INTERVAL/1000, + callback = function(spin) + G_reader_settings:saveSetting("ges_tap_interval", spin.value*1000) + GestureDetector:setNewInterval("ges_tap_interval", spin.value*1000) + end + } + UIManager:show(items) + end, + }, { text = _("Double tap interval"), + keep_menu_open = true, callback = function() local SpinWidget = require("ui/widget/spinwidget") local GestureDetector = require("device/gesturedetector") local items = SpinWidget:new{ - text = T(_([[ -Set double tap interval in milliseconds. -The interval value can range from 100 (0.1 seconds) to 2000 (2 seconds). + title_text = _("Double tap interval"), + info_text = T(_([[ +When double tap is enabled, this sets the time to wait for the second tap. A single tap will take at least this long to be detected. + +The interval value is in milliseconds and can range from 100 (0.1 seconds) to 2000 (2 seconds). Default value: %1]]), GestureDetector.DOUBLE_TAP_INTERVAL/1000), - width = math.floor(Screen:getWidth() * 0.6), + width = math.floor(Screen:getWidth() * 0.75), value = GestureDetector:getInterval("ges_double_tap_interval")/1000, value_min = 100, value_max = 2000, value_step = 100, value_hold_step = 500, ok_text = _("Set interval"), - title_text = _("Double tap interval"), default_value = GestureDetector.DOUBLE_TAP_INTERVAL/1000, callback = function(spin) G_reader_settings:saveSetting("ges_double_tap_interval", spin.value*1000) @@ -495,22 +528,24 @@ Default value: %1]]), GestureDetector.DOUBLE_TAP_INTERVAL/1000), }, { text = _("Two finger tap duration"), + keep_menu_open = true, callback = function() local SpinWidget = require("ui/widget/spinwidget") local GestureDetector = require("device/gesturedetector") local items = SpinWidget:new{ - text = T(_([[ -Set two finger tap duration in milliseconds. -The duration value can range from 100 (0.1 seconds) to 2000 (2 seconds). + title_text = _("Two finger tap duration"), + info_text = T(_([[ +This sets the allowed duration of any of the two fingers touch/release for the combined event to be considered a two finger tap. + +The duration value is in milliseconds and can range from 100 (0.1 seconds) to 2000 (2 seconds). Default value: %1]]), GestureDetector.TWO_FINGER_TAP_DURATION/1000), - width = math.floor(Screen:getWidth() * 0.6), + width = math.floor(Screen:getWidth() * 0.75), value = GestureDetector:getInterval("ges_two_finger_tap_duration")/1000, value_min = 100, value_max = 2000, value_step = 100, value_hold_step = 500, ok_text = _("Set duration"), - title_text = _("Two finger tap duration"), default_value = GestureDetector.TWO_FINGER_TAP_DURATION/1000, callback = function(spin) G_reader_settings:saveSetting("ges_two_finger_tap_duration", spin.value*1000) @@ -522,22 +557,24 @@ Default value: %1]]), GestureDetector.TWO_FINGER_TAP_DURATION/1000), }, { text = _("Hold interval"), + keep_menu_open = true, callback = function() local SpinWidget = require("ui/widget/spinwidget") local GestureDetector = require("device/gesturedetector") local items = SpinWidget:new{ - text = T(_([[ -Set hold interval in milliseconds. -The interval value can range from 100 (0.1 seconds) to 2000 (2 seconds). + title_text = _("Hold interval"), + info_text = T(_([[ +If a touch is not released in this interval, it is considered a hold (or long-press). On document's text, single word selection is then triggered. + +The interval value is in milliseconds and can range from 100 (0.1 seconds) to 2000 (2 seconds). Default value: %1]]), GestureDetector.HOLD_INTERVAL/1000), - width = math.floor(Screen:getWidth() * 0.6), + width = math.floor(Screen:getWidth() * 0.75), value = GestureDetector:getInterval("ges_hold_interval")/1000, value_min = 100, value_max = 2000, value_step = 100, value_hold_step = 500, ok_text = _("Set interval"), - title_text = _("Hold interval"), default_value = GestureDetector.HOLD_INTERVAL/1000, callback = function(spin) G_reader_settings:saveSetting("ges_hold_interval", spin.value*1000) @@ -549,22 +586,24 @@ Default value: %1]]), GestureDetector.HOLD_INTERVAL/1000), }, { text = _("Pan delay interval"), + keep_menu_open = true, callback = function() local SpinWidget = require("ui/widget/spinwidget") local GestureDetector = require("device/gesturedetector") local items = SpinWidget:new{ - text = T(_([[ -Set pan delay interval in milliseconds. -The interval value can range from 100 (0.1 seconds) to 2000 (2 seconds). + title_text = _("Pan delay interval"), + info_text = T(_([[ +This is used where necessary to reduce potential activation of panning when swiping is intended (e.g., for the menu or for multiswipe). + +The interval value is in milliseconds and can range from 100 (0.1 seconds) to 2000 (2 seconds). Default value: %1]]), GestureDetector.PAN_DELAYED_INTERVAL/1000), - width = math.floor(Screen:getWidth() * 0.6), + width = math.floor(Screen:getWidth() * 0.75), value = GestureDetector:getInterval("ges_pan_delayed_interval")/1000, value_min = 100, value_max = 2000, value_step = 100, value_hold_step = 500, ok_text = _("Set interval"), - title_text = _("Pan delay interval"), default_value = GestureDetector.PAN_DELAYED_INTERVAL/1000, callback = function(spin) G_reader_settings:saveSetting("ges_pan_delayed_interval", spin.value*1000) @@ -576,22 +615,24 @@ Default value: %1]]), GestureDetector.PAN_DELAYED_INTERVAL/1000), }, { text = _("Swipe interval"), + keep_menu_open = true, callback = function() local SpinWidget = require("ui/widget/spinwidget") local GestureDetector = require("device/gesturedetector") local items = SpinWidget:new{ - text = T(_([[ -Set swipe interval in milliseconds. -The interval value can range from 100 (0.1 seconds) to 2000 (2 seconds). + title_text = _("Swipe interval"), + info_text = T(_([[ +This sets the maximum delay between the start and the end of a swipe for it to be considered a swipe. Above this interval, it's considered panning. + +The interval value is in milliseconds and can range from 100 (0.1 seconds) to 2000 (2 seconds). Default value: %1]]), GestureDetector.SWIPE_INTERVAL/1000), - width = math.floor(Screen:getWidth() * 0.6), + width = math.floor(Screen:getWidth() * 0.75), value = GestureDetector:getInterval("ges_swipe_interval")/1000, value_min = 100, value_max = 2000, value_step = 100, value_hold_step = 500, ok_text = _("Set interval"), - title_text = _("Swipe interval"), default_value = GestureDetector.SWIPE_INTERVAL/1000, callback = function(spin) G_reader_settings:saveSetting("ges_swipe_interval", spin.value*1000)