mirror of
https://github.com/koreader/koreader.git
synced 2025-08-10 00:52:38 +00:00
Include a safety check to prevent rerecording an existing gesture. Also extend the default set of gestures to include all of them, and add east south west north for refresh. (I've always thought small diagonal swipe to be rather awkward to trigger.) See https://github.com/koreader/koreader/pull/4644#issuecomment-466743715 and https://github.com/koreader/koreader/issues/4651 regarding the menu update.
539 lines
21 KiB
Lua
539 lines
21 KiB
Lua
local ConfirmBox = require("ui/widget/confirmbox")
|
|
local DataStorage = require("datastorage")
|
|
local Device = require("device")
|
|
local Event = require("ui/event")
|
|
local Geom = require("ui/geometry")
|
|
local GestureRange = require("ui/gesturerange")
|
|
local InfoMessage = require("ui/widget/infomessage")
|
|
local InputContainer = require("ui/widget/container/inputcontainer")
|
|
local InputDialog = require("ui/widget/inputdialog")
|
|
local LuaData = require("luadata")
|
|
local Screen = require("device").screen
|
|
local UIManager = require("ui/uimanager")
|
|
local T = require("ffi/util").template
|
|
local _ = require("gettext")
|
|
local logger = require("logger")
|
|
|
|
local default_gesture = {
|
|
tap_right_bottom_corner = "nothing",
|
|
tap_left_bottom_corner = Device:hasFrontlight() and "toggle_frontlight" or "nothing",
|
|
short_diagonal_swipe = "full_refresh",
|
|
multiswipe = "nothing", -- otherwise registerGesture() won't pick up on multiswipes
|
|
multiswipe_west_east = "previous_location",
|
|
multiswipe_east_south_west_north = "full_refresh",
|
|
}
|
|
|
|
local ReaderGesture = InputContainer:new{
|
|
multiswipes_enabled = G_reader_settings:readSetting("multiswipes_enabled"),
|
|
}
|
|
|
|
local custom_multiswipes_path = DataStorage:getSettingsDir().."/multiswipes.lua"
|
|
local custom_multiswipes = LuaData:open(custom_multiswipes_path, { name = "MultiSwipes" })
|
|
local custom_multiswipes_table = custom_multiswipes:readSetting("multiswipes")
|
|
|
|
local default_multiswipes = {
|
|
"west east",
|
|
"east west",
|
|
"north south",
|
|
"south north",
|
|
"north west",
|
|
"north east",
|
|
"south west",
|
|
"south east",
|
|
"east north",
|
|
"west north",
|
|
"east south",
|
|
"west south",
|
|
"east south west north",
|
|
}
|
|
local multiswipes = {}
|
|
local multiswipes_info_text = _([[
|
|
Multiswipes allow you to perform complex gestures built up out of multiple straight swipes.]])
|
|
|
|
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:genMultiswipeSubmenu()
|
|
return {
|
|
text = _("Multiswipe"),
|
|
sub_item_table = self:buildMultiswipeMenu(),
|
|
enabled_func = function() return self.multiswipes_enabled end,
|
|
separator = true,
|
|
}
|
|
end
|
|
|
|
function ReaderGesture:addToMainMenu(menu_items)
|
|
menu_items.gesture = {
|
|
text = _("Gesture manager"),
|
|
sub_item_table = {
|
|
{
|
|
text = _("Enable multiswipes"),
|
|
checked_func = function() return self.multiswipes_enabled end,
|
|
callback = function()
|
|
G_reader_settings:saveSetting("multiswipes_enabled", not self.multiswipes_enabled)
|
|
self.multiswipes_enabled = G_reader_settings:isTrue("multiswipes_enabled")
|
|
end,
|
|
help_text = multiswipes_info_text,
|
|
},
|
|
{
|
|
text = _("Multiswipe recorder"),
|
|
enabled_func = function() return self.multiswipes_enabled end,
|
|
callback = function(touchmenu_instance)
|
|
local multiswipe_recorder
|
|
multiswipe_recorder = InputDialog:new{
|
|
title = _("Multiswipe recorder"),
|
|
input_hint = _("Make a multiswipe gesture"),
|
|
buttons = {
|
|
{
|
|
{
|
|
text = _("Cancel"),
|
|
callback = function()
|
|
UIManager:close(multiswipe_recorder)
|
|
end,
|
|
},
|
|
{
|
|
text = _("Save"),
|
|
is_enter_default = true,
|
|
callback = function()
|
|
local recorded_multiswipe = multiswipe_recorder._raw_multiswipe
|
|
if not recorded_multiswipe then return end
|
|
logger.dbg("Multiswipe recorder detected:", recorded_multiswipe)
|
|
|
|
for k, multiswipe in pairs(multiswipes) do
|
|
if recorded_multiswipe == multiswipe then
|
|
UIManager:show(InfoMessage:new{
|
|
text = _("Recorded multiswipe already exists."),
|
|
show_icon = false,
|
|
timeout = 5,
|
|
})
|
|
return
|
|
end
|
|
end
|
|
|
|
custom_multiswipes:addTableItem("multiswipes", recorded_multiswipe)
|
|
-- TODO implement some nicer method in TouchMenu than this ugly hack for updating the menu
|
|
touchmenu_instance.item_table[3] = self:genMultiswipeSubmenu()
|
|
UIManager:close(multiswipe_recorder)
|
|
end,
|
|
},
|
|
}
|
|
},
|
|
}
|
|
|
|
multiswipe_recorder.ges_events.Multiswipe = {
|
|
GestureRange:new{
|
|
ges = "multiswipe",
|
|
range = Geom:new{
|
|
x = 0, y = 0,
|
|
w = Screen:getWidth(),
|
|
h = Screen:getHeight(),
|
|
},
|
|
doc = "Multiswipe in gesture creator"
|
|
}
|
|
}
|
|
|
|
function multiswipe_recorder:onMultiswipe(arg, ges)
|
|
multiswipe_recorder._raw_multiswipe = ges.multiswipe_directions
|
|
multiswipe_recorder:setInputText(ReaderGesture:friendlyMultiswipeName(multiswipe_recorder._raw_multiswipe))
|
|
end
|
|
|
|
UIManager:show(multiswipe_recorder)
|
|
end,
|
|
help_text = _("The number of possible multiswipe gestures is theoretically infinite. With the multiswipe recorder you can easily record your own."),
|
|
},
|
|
-- NB If this changes from position 3, also update the position of this menu in multigesture recorder callback
|
|
self:genMultiswipeSubmenu(),
|
|
{
|
|
text = _("Tap bottom left corner"),
|
|
sub_item_table = self:buildMenu("tap_left_bottom_corner", default_gesture["tap_left_bottom_corner"]),
|
|
},
|
|
{
|
|
text = _("Tap bottom right corner"),
|
|
sub_item_table = self:buildMenu("tap_right_bottom_corner", default_gesture["tap_right_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 = {
|
|
{_("Nothing"), "nothing", true },
|
|
{_("Back 10 pages"), "page_jmp_back_10", not self.is_docless},
|
|
{_("Previous page"), "page_jmp_back_1", not self.is_docless},
|
|
{_("Forward 10 pages"), "page_jmp_fwd_10", not self.is_docless},
|
|
{_("Next page"), "page_jmp_fwd_1", not self.is_docless},
|
|
{_("Back to previous location"), "previous_location", not self.is_docless},
|
|
{_("Folder up"), "folder_up", self.is_docless},
|
|
{_("Bookmarks"), "bookmarks", not self.is_docless},
|
|
{_("History"), "history", true},
|
|
{_("Open previous document"), "open_previous_document", true, true},
|
|
{_("Table of contents"), "toc", not self.is_docless},
|
|
{_("Reading progress"), "reading_progress", ReaderGesture.getReaderProgress ~= nil},
|
|
{_("Full screen refresh"), "full_refresh", true},
|
|
{_("Night mode"), "night_mode", true},
|
|
{_("Suspend"), "suspend", true},
|
|
{_("Toggle frontlight"), "toggle_frontlight", Device:hasFrontlight()},
|
|
{_("Toggle accelerometer"), "toggle_gsensor", Device:canToggleGSensor()},
|
|
{_("Toggle rotation"), "toggle_rotation", not self.is_docless},
|
|
{_("Zoom to fit content width"), "zoom_contentwidth", not self.is_docless},
|
|
{_("Zoom to fit content height"), "zoom_contentheight", not self.is_docless},
|
|
{_("Zoom to fit page width"), "zoom_pagewidth", not self.is_docless},
|
|
{_("Zoom to fit page height"), "zoom_pageheight", not self.is_docless},
|
|
{_("Zoom to fit column"), "zoom_column", not self.is_docless},
|
|
{_("Zoom to fit content"), "zoom_content", not self.is_docless},
|
|
{_("Zoom to fit page"), "zoom_page", not self.is_docless},
|
|
}
|
|
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))
|
|
|
|
if not gesture_manager[ges] then
|
|
gesture_manager[ges] = default
|
|
G_reader_settings:saveSetting(self.ges_mode, gesture_manager)
|
|
end
|
|
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
|
|
local sep = entry[2] == "nothing" or entry[4] == true
|
|
table.insert(return_menu, self:createSubMenu(entry[1], entry[2], ges, sep))
|
|
end
|
|
end
|
|
return return_menu
|
|
end
|
|
|
|
function ReaderGesture:buildMultiswipeMenu()
|
|
local menu = {}
|
|
multiswipes = {}
|
|
|
|
for k, v in pairs(default_multiswipes) do
|
|
table.insert(multiswipes, v)
|
|
end
|
|
|
|
if custom_multiswipes_table then
|
|
for k, v in pairs(custom_multiswipes_table) do
|
|
table.insert(multiswipes, v)
|
|
end
|
|
end
|
|
|
|
for i=1, #multiswipes do
|
|
local multiswipe = multiswipes[i]
|
|
local friendly_multiswipe_name = self:friendlyMultiswipeName(multiswipe)
|
|
local safe_multiswipe_name = "multiswipe_"..self:safeMultiswipeName(multiswipe)
|
|
local default_action = default_gesture[safe_multiswipe_name] and default_gesture[safe_multiswipe_name] or "nothing"
|
|
table.insert(menu, {
|
|
text = friendly_multiswipe_name,
|
|
sub_item_table = self:buildMenu(safe_multiswipe_name, default_action),
|
|
hold_callback = function(touchmenu_instance)
|
|
if i > #default_multiswipes then
|
|
UIManager:show(ConfirmBox:new{
|
|
text = T(_("Remove custom multiswipe %1?"), friendly_multiswipe_name),
|
|
ok_text = _("Remove"),
|
|
ok_callback = function()
|
|
-- multiswipes are a combined table, first defaults, then custom
|
|
-- so the right index is minus #defalt_multiswipes
|
|
custom_multiswipes:removeTableItem("multiswipes", i-#default_multiswipes)
|
|
touchmenu_instance.item_table = self:buildMultiswipeMenu()
|
|
touchmenu_instance:updateItems()
|
|
end,
|
|
})
|
|
end
|
|
end,
|
|
})
|
|
end
|
|
|
|
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
|
|
|
|
local multiswipe_to_arrow = {
|
|
east = "↦",
|
|
west = "↤",
|
|
north = "↥",
|
|
south = "↧",
|
|
}
|
|
function ReaderGesture:friendlyMultiswipeName(multiswipe)
|
|
for k, v in pairs(multiswipe_to_arrow) do
|
|
multiswipe = multiswipe:gsub(k, v)
|
|
end
|
|
return multiswipe
|
|
end
|
|
|
|
function ReaderGesture:safeMultiswipeName(multiswipe)
|
|
return multiswipe:gsub(" ", "_")
|
|
end
|
|
|
|
function ReaderGesture:setupGesture(ges, action)
|
|
local ges_type
|
|
local zone
|
|
local overrides
|
|
local direction, distance
|
|
if ges == "multiswipe" then
|
|
ges_type = "multiswipe"
|
|
zone = {
|
|
ratio_x = 0.0, ratio_y = 0,
|
|
ratio_w = 1, ratio_h = 1,
|
|
}
|
|
direction = {
|
|
northeast = true, northwest = true,
|
|
southeast = true, southwest = true,
|
|
east = true, west = true,
|
|
north = true, south = true,
|
|
}
|
|
elseif 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
|
|
|
|
if ges == "multiswipe" then
|
|
if self.multiswipes_enabled == nil then
|
|
UIManager:show(ConfirmBox:new{
|
|
text = _("You have just performed a multiswipe gesture for the first time.") .."\n\n".. multiswipes_info_text,
|
|
ok_text = _("Enable"),
|
|
ok_callback = function()
|
|
G_reader_settings:saveSetting("multiswipes_enabled", true)
|
|
self.multiswipes_enabled = true
|
|
end,
|
|
cancel_text = _("Disable"),
|
|
cancel_callback = function()
|
|
G_reader_settings:saveSetting("multiswipes_enabled", false)
|
|
self.multiswipes_enabled = false
|
|
end,
|
|
})
|
|
else
|
|
return self:multiswipeAction(gest.multiswipe_directions)
|
|
end
|
|
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("all", "full")
|
|
G_reader_settings:saveSetting("night_mode", not night_mode)
|
|
elseif action == "full_refresh" then
|
|
if self.view then
|
|
-- update footer (time & battery)
|
|
self.view.footer:updateFooter()
|
|
end
|
|
UIManager:setDirty("all", "full")
|
|
elseif action == "bookmarks" then
|
|
self.ui:handleEvent(Event:new("ShowBookmark"))
|
|
elseif action == "history" then
|
|
self.ui:handleEvent(Event:new("ShowHist"))
|
|
elseif action == "page_jmp_fwd_10" then
|
|
self:pageUpdate(10)
|
|
elseif action == "page_jmp_fwd_1" then
|
|
self:pageUpdate(1)
|
|
elseif action == "page_jmp_back_10" then
|
|
self:pageUpdate(-10)
|
|
elseif action == "page_jmp_back_1" then
|
|
self:pageUpdate(-1)
|
|
elseif action == "previous_location" then
|
|
self.ui:handleEvent(Event:new("GoBackLink"))
|
|
elseif action == "folder_up" then
|
|
self.ui.file_chooser:changeToPath(string.format("%s/..", self.ui.file_chooser.path))
|
|
elseif action == "open_previous_document" then
|
|
-- FileManager
|
|
if self.ui.menu.openLastDoc and G_reader_settings:readSetting("lastfile") ~= nil then
|
|
self.ui.menu:openLastDoc()
|
|
-- ReaderUI
|
|
elseif self.ui.switchDocument and self.ui.menu then
|
|
self.ui:switchDocument(self.ui.menu:getPreviousFile())
|
|
end
|
|
elseif action == "toggle_frontlight" then
|
|
Device:getPowerDevice():toggleFrontlight()
|
|
self:onShowFLOnOff()
|
|
elseif action == "toggle_gsensor" then
|
|
G_reader_settings:flipNilOrFalse("input_ignore_gsensor")
|
|
Device:toggleGSensor()
|
|
self:onGSensorToggle()
|
|
elseif action == "toggle_rotation" then
|
|
if Screen:getScreenMode() == "portrait" then
|
|
self.ui:handleEvent(Event:new("SetScreenMode", "landscape"))
|
|
else
|
|
self.ui:handleEvent(Event:new("SetScreenMode", "portrait"))
|
|
end
|
|
elseif action == "suspend" then
|
|
UIManager:suspend()
|
|
elseif action == "zoom_contentwidth" then
|
|
self.ui:handleEvent(Event:new("SetZoomMode", "contentwidth"))
|
|
elseif action == "zoom_contentheight" then
|
|
self.ui:handleEvent(Event:new("SetZoomMode", "contentheight"))
|
|
elseif action == "zoom_pagewidth" then
|
|
self.ui:handleEvent(Event:new("SetZoomMode", "pagewidth"))
|
|
elseif action == "zoom_pageheight" then
|
|
self.ui:handleEvent(Event:new("SetZoomMode", "pageheight"))
|
|
elseif action == "zoom_column" then
|
|
self.ui:handleEvent(Event:new("SetZoomMode", "column"))
|
|
elseif action == "zoom_content" then
|
|
self.ui:handleEvent(Event:new("SetZoomMode", "content"))
|
|
elseif action == "zoom_page" then
|
|
self.ui:handleEvent(Event:new("SetZoomMode", "page"))
|
|
end
|
|
return true
|
|
end
|
|
|
|
function ReaderGesture:multiswipeAction(multiswipe_directions)
|
|
local gesture_manager = G_reader_settings:readSetting(self.ges_mode)
|
|
local multiswipe_gesture_name = "multiswipe_"..self:safeMultiswipeName(multiswipe_directions)
|
|
for gesture, action in pairs(gesture_manager) do
|
|
if gesture == multiswipe_gesture_name then
|
|
return self:gestureAction(action)
|
|
end
|
|
end
|
|
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
|
|
|
|
function ReaderGesture:onGSensorToggle()
|
|
local Notification = require("ui/widget/notification")
|
|
local new_text
|
|
if G_reader_settings:isTrue("input_ignore_gsensor") then
|
|
new_text = _("Accelerometer rotation events will now be ignored.")
|
|
else
|
|
new_text = _("Accelerometer rotation events will now be honored.")
|
|
end
|
|
UIManager:show(Notification:new{
|
|
text = new_text,
|
|
timeout = 1.0,
|
|
})
|
|
return true
|
|
end
|
|
|
|
return ReaderGesture
|