Gesture manager (initial) (#4240)

Manager supports separate gestures for the file manager and the reader. Gestures from the manager have a higher priority than built-in gestures.

Settings available in Gear -> Device -> Gesture manager
pull/4250/head
Robert 6 years ago committed by Frans de Jonge
parent 3d128b543b
commit dc5a479b66

@ -24,6 +24,7 @@ local MultiConfirmBox = require("ui/widget/multiconfirmbox")
local PluginLoader = require("pluginloader")
local ReaderDeviceStatus = require("apps/reader/modules/readerdevicestatus")
local ReaderDictionary = require("apps/reader/modules/readerdictionary")
local ReaderGesture = require("apps/reader/modules/readergesture")
local ReaderUI = require("apps/reader/readerui")
local ReaderWikipedia = require("apps/reader/modules/readerwikipedia")
local Screenshoter = require("ui/widget/screenshoter")
@ -147,6 +148,8 @@ function FileManager:init()
end
end,
close_callback = function() return self:onClose() end,
-- allow left bottom tap gesture, otherwise it is eaten by hidden return button
return_arrow_propagation = true,
}
self.file_chooser = file_chooser
self.focused_file = nil -- use it only once
@ -355,6 +358,10 @@ function FileManager:init()
end
end
if Device:isTouchDevice() then
table.insert(self, ReaderGesture:new{ ui = self })
end
if Device:hasKeys() then
self.key_events.Home = { {"Home"}, doc = "go home" }
--Override the menu.lua way of handling the back key

@ -0,0 +1,233 @@
local Device = require("device")
local Event = require("ui/event")
local InputContainer = require("ui/widget/container/inputcontainer")
local Screen = require("device").screen
local UIManager = require("ui/uimanager")
local T = require("ffi/util").template
local _ = require("gettext")
local default_gesture = {
tap_right_bottom_corner = "nothing",
tap_left_bottom_corner = Device.hasFrontlight() and "toggle_frontlight" or "nothing",
short_diagonal_swipe = "full_refresh",
}
local ReaderGesture = InputContainer:new{
}
function ReaderGesture:init()
if not Device:isTouchDevice() then return end
self.is_docless = self.ui == nil or self.ui.document == nil
self.ges_mode = self.is_docless and "gesture_fm" or "gesture_reader"
local gm = G_reader_settings:readSetting(self.ges_mode)
if gm == nil then G_reader_settings:saveSetting(self.ges_mode, {}) end
self.ui.menu:registerToMainMenu(self)
self:initGesture()
end
function ReaderGesture:initGesture()
local gesture_manager = G_reader_settings:readSetting(self.ges_mode)
for gesture, action in pairs(default_gesture) do
if not gesture_manager[gesture] then
gesture_manager[gesture] = action
end
end
for gesture, action in pairs(gesture_manager) do
self:setupGesture(gesture, action)
end
G_reader_settings:saveSetting(self.ges_mode, gesture_manager)
end
function ReaderGesture:addToMainMenu(menu_items)
menu_items.gesture = {
text = _("Gesture manager"),
sub_item_table = {
{
text = _("Tap right bottom corner"),
sub_item_table = self:buildMenu("tap_right_bottom_corner", default_gesture["tap_right_bottom_corner"])
},
{
text = _("Tap left bottom corner"),
sub_item_table = self:buildMenu("tap_left_bottom_corner", default_gesture["tap_left_bottom_corner"])
},
{
text = _("Short diagonal swipe"),
sub_item_table = self:buildMenu("short_diagonal_swipe", default_gesture["short_diagonal_swipe"])
},
},
}
end
function ReaderGesture:buildMenu(ges, default)
local gesture_manager = G_reader_settings:readSetting(self.ges_mode)
local menu = {
--{_("Menu element), "action", enable_element},
{_("Nothing"), "nothing", true },
{_("Back 10 pages"), "page_update_down10", not self.is_docless},
{_("Forward 10 pages"), "page_update_up10", not self.is_docless},
{_("Folder up"), "folder_up", self.is_docless},
{_("Bookmarks"), "bookmarks", not self.is_docless},
{_("Table of content"), "toc", not self.is_docless},
{_("Reading progress"), "reading_progress", ReaderGesture.getReaderProgress ~= nil},
{_("Full screen refresh"), "full_refresh", true},
{_("Night mode"), "night_mode", true},
{_("Toggle frontlight"), "toggle_frontlight", Device.hasFrontlight()},
}
local return_menu = {}
-- add default action to the top of the submenu
for __, entry in pairs(menu) do
if entry[2] == default then
local menu_entry_default = T(_("%1 (default)"), entry[1])
table.insert(return_menu, self:createSubMenu(menu_entry_default, entry[2], ges, true))
break
end
end
-- another elements
for _, entry in pairs(menu) do
if not entry[3] and gesture_manager[ges] == entry[2] then
gesture_manager[ges] = "nothing"
G_reader_settings:saveSetting(self.ges_mode, gesture_manager)
end
if entry[2] ~= default and entry[3] then
table.insert(return_menu, self:createSubMenu(entry[1], entry[2], ges, entry[2] == "nothing"))
end
end
return return_menu
end
function ReaderGesture:createSubMenu(text, action, ges, separator)
local gesture_manager = G_reader_settings:readSetting(self.ges_mode)
return {
text = text,
checked_func = function()
return gesture_manager[ges] == action
end,
callback = function()
gesture_manager[ges] = action
G_reader_settings:saveSetting(self.ges_mode, gesture_manager)
self:setupGesture(ges, action)
end,
separator = separator or false,
}
end
function ReaderGesture:setupGesture(ges, action)
local ges_type
local zone
local overrides
local direction, distance
if ges == "tap_right_bottom_corner" then
ges_type = "tap"
zone = {
ratio_x = 0.9, ratio_y = 0.9,
ratio_w = 0.1, ratio_h = 0.1,
}
if self.is_docless then
overrides = { 'filemanager_tap' }
else
overrides = { 'readerfooter_tap', }
end
elseif ges == "tap_left_bottom_corner" then
ges_type = "tap"
zone = {
ratio_x = 0.0, ratio_y = 0.9,
ratio_w = 0.1, ratio_h = 0.1,
}
if self.is_docless then
overrides = { 'filemanager_tap' }
else
overrides = { 'readerfooter_tap', 'filemanager_tap' }
end
elseif ges == "short_diagonal_swipe" then
ges_type = "swipe"
zone = {
ratio_x = 0.0, ratio_y = 0,
ratio_w = 1, ratio_h = 1,
}
direction = {northeast = true, northwest = true, southeast = true, southwest = true}
distance = "short"
if self.is_docless then
overrides = { 'filemanager_tap' }
else
overrides = { 'rolling_swipe', 'paging_swipe' }
end
else return
end
self:registerGesture(ges, action, ges_type, zone, overrides, direction, distance)
end
function ReaderGesture:registerGesture(ges, action, ges_type, zone, overrides, direction, distance)
self.ui:registerTouchZones({
{
id = ges,
ges = ges_type,
screen_zone = zone,
handler = function(gest)
if distance == "short" and gest.distance > Screen:scaleBySize(300) then return end
if direction and not direction[gest.direction] then return end
return self:gestureAction(action)
end,
overrides = overrides,
},
})
end
function ReaderGesture:gestureAction(action)
if action == "reading_progress" and ReaderGesture.getReaderProgress then
UIManager:show(ReaderGesture.getReaderProgress())
elseif action == "toc" then
self.ui:handleEvent(Event:new("ShowToc"))
elseif action == "night_mode" then
local night_mode = G_reader_settings:readSetting("night_mode") or false
Screen:toggleNightMode()
UIManager:setDirty(nil, "full")
G_reader_settings:saveSetting("night_mode", not night_mode)
elseif action == "full_refresh" then
UIManager:setDirty(nil, "full")
elseif action == "bookmarks" then
self.ui:handleEvent(Event:new("ShowBookmark"))
elseif action =="page_update_up10" then
self:pageUpdate(10)
elseif action =="page_update_down10" then
self:pageUpdate(-10)
elseif action =="folder_up" then
self.ui.file_chooser:changeToPath(string.format("%s/..", self.ui.file_chooser.path))
elseif action =="toggle_frontlight" then
Device:getPowerDevice():toggleFrontlight()
self:onShowFLOnOff()
end
return true
end
function ReaderGesture:pageUpdate(page)
local curr_page
if self.document.info.has_pages then
curr_page = self.ui.paging.current_page
else
curr_page = self.document:getCurrentPage()
end
if curr_page and page then
curr_page = curr_page + page
self.ui:handleEvent(Event:new("GotoPage", curr_page))
end
end
function ReaderGesture:onShowFLOnOff()
local Notification = require("ui/widget/notification")
local powerd = Device:getPowerDevice()
local new_text
if powerd.is_fl_on then
new_text = _("Frontlight is on.")
else
new_text = _("Frontlight is off.")
end
UIManager:show(Notification:new{
text = new_text,
timeout = 1.0,
})
return true
end
return ReaderGesture

@ -26,6 +26,7 @@ local ReaderDeviceStatus = require("apps/reader/modules/readerdevicestatus")
local ReaderDictionary = require("apps/reader/modules/readerdictionary")
local ReaderFont = require("apps/reader/modules/readerfont")
local ReaderFrontLight = require("apps/reader/modules/readerfrontlight")
local ReaderGesture = require("apps/reader/modules/readergesture")
local ReaderGoto = require("apps/reader/modules/readergoto")
local ReaderHinting = require("apps/reader/modules/readerhinting")
local ReaderHighlight = require("apps/reader/modules/readerhighlight")
@ -351,6 +352,13 @@ function ReaderUI:init()
"at", plugin_module.path)
end
end
if Device:isTouchDevice() then
-- gesture manager
self:registerModule("gesture", ReaderGesture:new {
document = self.document,
ui = self,
})
end
-- we only read settings after all the widgets are initialized
self:handleEvent(Event:new("ReadSettings", self.doc_settings))

@ -55,6 +55,14 @@ function DepGraph:removeNode(node_key)
end
end
function DepGraph:checkNode(id)
if self.nodes[id] then
return true
else
return false
end
end
function DepGraph:addNodeDep(node_key, dep_node_key)
local node = self.nodes[node_key]
if not node then
@ -65,6 +73,16 @@ function DepGraph:addNodeDep(node_key, dep_node_key)
table.insert(node.deps, dep_node_key)
end
function DepGraph:removeNodeDep(node_key, dep_node_key)
local node = self.nodes[node_key]
if not node.deps then node.deps = {} end
for i, dep_key in ipairs(node.deps) do
if dep_key == dep_node_key then
self.nodes[node_key]["deps"][i] = nil
end
end
end
function DepGraph:serialize()
local visited = {}
local ordered_nodes = {}

@ -36,6 +36,7 @@ local order = {
device = {
"time",
"battery",
"gesture",
},
network = {
"network_wifi",

@ -57,6 +57,7 @@ local order = {
device = {
"time",
"battery",
"gesture",
},
network = {
"network_wifi",

@ -160,6 +160,42 @@ function InputContainer:registerTouchZones(zones)
end
end
function InputContainer:unRegisterTouchZones(zones)
if self.touch_zone_dg then
for i, zone in ipairs(zones) do
if self._zones[zone.id] then
self.touch_zone_dg:removeNode(zone.id)
if zone.overrides then
for _, override_zone_id in ipairs(zone.overrides) do
--self.touch_zone_dg:removeNodeDep(override_zone_id, zone.id)
self.touch_zone_dg:removeNodeDep(override_zone_id, zone.id)
end
end
for _, id in ipairs(self._ordered_touch_zones) do
if id.def.id == zone.id then
table.remove(self._ordered_touch_zones, i)
break
end
end
end
end
self._ordered_touch_zones = {}
if self.touch_zone_dg then
for _, zone_id in ipairs(self.touch_zone_dg:serialize()) do
table.insert(self._ordered_touch_zones, self._zones[zone_id])
end
end
end
end
function InputContainer:checkRegisterTouchZone(id)
if self.touch_zone_dg then
return self.touch_zone_dg:checkNode(id)
else
return false
end
end
--[[--
Updates touch zones based on new screen dimensions.

@ -698,6 +698,7 @@ function Menu:init()
callback = function() self:onReturn() end,
bordersize = 0,
show_parent = self,
readonly = self.return_arrow_propagation,
}
self.page_return_arrow:hide()
self.return_button = HorizontalGroup:new{
@ -1188,8 +1189,13 @@ function Menu:onSwipe(arg, ges_ev)
-- no use for now
do end -- luacheck: ignore 541
else -- diagonal swipe
-- trigger full refresh
UIManager:setDirty(nil, "full")
if G_reader_settings:readSetting("gesture_fm") and G_reader_settings:readSetting("gesture_fm")["short_diagonal_swipe"] then
-- managed by gesture manager
do end -- luacheck: ignore 541
else
-- trigger full refresh
UIManager:setDirty(nil, "full")
end
end
end

@ -16,7 +16,6 @@ local WidgetContainer = require("ui/widget/container/widgetcontainer")
local T = require("ffi/util").template
local _ = require("gettext")
local tap_touch_zone_ratio = { x = 0, y = 15/16, w = 1/10, h = 1/16, }
local swipe_touch_zone_ratio = { x = 0, y = 1/8, w = 1/10, h = 7/8, }
local swipe_touch_zone_ratio_warmth = { x = 7/8, y = 1/8, w = 1/8, h = 7/8, }
@ -60,16 +59,6 @@ function KoboLight:setupTouchZones()
ratio_h = swipe_touch_zone_ratio_warmth.h,
}
self.ui:registerTouchZones({
{
id = "plugin_kobolight_tap",
ges = "tap",
screen_zone = {
ratio_x = tap_touch_zone_ratio.x, ratio_y = tap_touch_zone_ratio.y,
ratio_w = tap_touch_zone_ratio.w, ratio_h = tap_touch_zone_ratio.h,
},
handler = function() return self:onTap() end,
overrides = { 'readerfooter_tap' },
},
{
id = "plugin_kobolight_swipe",
ges = "swipe",
@ -165,12 +154,6 @@ function KoboLight:onShowOnOff()
return true
end
function KoboLight:onTap()
Device:getPowerDevice():toggleFrontlight()
self:onShowOnOff()
return true
end
function KoboLight:onSwipe(_, ges)
local powerd = Device:getPowerDevice()
if powerd.fl_intensity == nil then return false end

@ -124,6 +124,26 @@ function ReaderStatistics:init()
end
return readingprogress
end
local ReaderGesture = require("apps/reader/modules/readergesture")
ReaderGesture.getReaderProgress = function()
local readingprogress
self:insertDB(self.id_curr_book)
local current_period, current_pages = self:getCurrentBookStats()
local today_period, today_pages = self:getTodayBookStats()
local dates_stats = self:getReadingProgressStats(7)
if dates_stats then
readingprogress = ReaderProgress:new{
dates = dates_stats,
current_period = current_period,
current_pages = current_pages,
today_period = today_period,
today_pages = today_pages,
--readonly = true,
}
end
return readingprogress
end
end
function ReaderStatistics:initData()

Loading…
Cancel
Save