diff --git a/frontend/apps/filemanager/filemanager.lua b/frontend/apps/filemanager/filemanager.lua index 52717679b..6d7d8a427 100644 --- a/frontend/apps/filemanager/filemanager.lua +++ b/frontend/apps/filemanager/filemanager.lua @@ -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 diff --git a/frontend/apps/reader/modules/readergesture.lua b/frontend/apps/reader/modules/readergesture.lua new file mode 100644 index 000000000..08316f4ef --- /dev/null +++ b/frontend/apps/reader/modules/readergesture.lua @@ -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 diff --git a/frontend/apps/reader/readerui.lua b/frontend/apps/reader/readerui.lua index 6caee98e2..1d1594428 100644 --- a/frontend/apps/reader/readerui.lua +++ b/frontend/apps/reader/readerui.lua @@ -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)) diff --git a/frontend/depgraph.lua b/frontend/depgraph.lua index 2a1d3862e..d15d69da3 100644 --- a/frontend/depgraph.lua +++ b/frontend/depgraph.lua @@ -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 = {} diff --git a/frontend/ui/elements/filemanager_menu_order.lua b/frontend/ui/elements/filemanager_menu_order.lua index 6194f13fd..63533063b 100644 --- a/frontend/ui/elements/filemanager_menu_order.lua +++ b/frontend/ui/elements/filemanager_menu_order.lua @@ -36,6 +36,7 @@ local order = { device = { "time", "battery", + "gesture", }, network = { "network_wifi", diff --git a/frontend/ui/elements/reader_menu_order.lua b/frontend/ui/elements/reader_menu_order.lua index 957153e3b..8825baefb 100644 --- a/frontend/ui/elements/reader_menu_order.lua +++ b/frontend/ui/elements/reader_menu_order.lua @@ -57,6 +57,7 @@ local order = { device = { "time", "battery", + "gesture", }, network = { "network_wifi", diff --git a/frontend/ui/widget/container/inputcontainer.lua b/frontend/ui/widget/container/inputcontainer.lua index 8e69fc238..af499474a 100644 --- a/frontend/ui/widget/container/inputcontainer.lua +++ b/frontend/ui/widget/container/inputcontainer.lua @@ -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. diff --git a/frontend/ui/widget/menu.lua b/frontend/ui/widget/menu.lua index fafb8d92c..d471337fb 100644 --- a/frontend/ui/widget/menu.lua +++ b/frontend/ui/widget/menu.lua @@ -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 diff --git a/plugins/kobolight.koplugin/main.lua b/plugins/kobolight.koplugin/main.lua index 31c077cfb..3dcfb7b6b 100644 --- a/plugins/kobolight.koplugin/main.lua +++ b/plugins/kobolight.koplugin/main.lua @@ -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 diff --git a/plugins/statistics.koplugin/main.lua b/plugins/statistics.koplugin/main.lua index 28faf2df2..ee53e2d80 100755 --- a/plugins/statistics.koplugin/main.lua +++ b/plugins/statistics.koplugin/main.lua @@ -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()