diff --git a/frontend/apps/reader/modules/readerfooter.lua b/frontend/apps/reader/modules/readerfooter.lua index 4ab014590..f190131ea 100644 --- a/frontend/apps/reader/modules/readerfooter.lua +++ b/frontend/apps/reader/modules/readerfooter.lua @@ -228,6 +228,7 @@ function ReaderFooter:setupTouchZones() ges = "hold", screen_zone = footer_screen_zone, handler = function() return self:onHoldFooter() end, + overrides = {'readerhighlight_hold'}, }, }) end diff --git a/frontend/apps/reader/modules/readerhighlight.lua b/frontend/apps/reader/modules/readerhighlight.lua index e78546141..8c01384ed 100644 --- a/frontend/apps/reader/modules/readerhighlight.lua +++ b/frontend/apps/reader/modules/readerhighlight.lua @@ -1,7 +1,4 @@ local InputContainer = require("ui/widget/container/inputcontainer") -local GestureRange = require("ui/gesturerange") -local Geom = require("ui/geometry") -local Screen = require("device").screen local Device = require("device") local Event = require("ui/event") local UIManager = require("ui/uimanager") @@ -18,50 +15,53 @@ function ReaderHighlight:init() end) end -function ReaderHighlight:initGesListener() - self.ges_events = { - Tap = { - GestureRange:new{ - ges = "tap", - range = Geom:new{ - x = 0, y = 0, - w = Screen:getWidth(), - h = Screen:getHeight() - } - } +function ReaderHighlight:setupTouchZones() + -- deligate gesture listener to readerui + self.ges_events = {} + self.onGesture = nil + + if not Device:isTouchDevice() then return end + + self.ui:registerTouchZones({ + { + id = "readerhighlight_tap", + ges = "tap", + screen_zone = { + ratio_x = 0, ratio_y = 0, ratio_w = 1, ratio_h = 1, + }, + overrides = { 'tap_forward', 'tap_backward', }, + handler = function(ges) return self:onTap(nil, ges) end }, - Hold = { - GestureRange:new{ - ges = "hold", - range = Geom:new{ - x = 0, y = 0, - w = Screen:getWidth(), - h = Screen:getHeight() - } - } + { + id = "readerhighlight_hold", + ges = "hold", + screen_zone = { + ratio_x = 0, ratio_y = 0, ratio_w = 1, ratio_h = 1, + }, + handler = function(ges) return self:onHold(nil, ges) end }, - HoldRelease = { - GestureRange:new{ - ges = "hold_release", - range = Geom:new{ - x = 0, y = 0, - w = Screen:getWidth(), - h = Screen:getHeight() - } - } + { + id = "readerhighlight_hold_release", + ges = "hold_release", + screen_zone = { + ratio_x = 0, ratio_y = 0, ratio_w = 1, ratio_h = 1, + }, + handler = function() return self:onHoldRelease() end }, - HoldPan = { - GestureRange:new{ - ges = "hold_pan", - range = Geom:new{ - x = 0, y = 0, - w = Screen:getWidth(), - h = Screen:getHeight() - }, - rate = 2.0, - } + { + id = "readerhighlight_hold_pan", + ges = "hold_pan", + rate = 2.0, + screen_zone = { + ratio_x = 0, ratio_y = 0, ratio_w = 1, ratio_h = 1, + }, + handler = function(ges) return self:onHoldPan(nil, ges) end }, - } + }) +end + +function ReaderHighlight:onReaderReady() + self:setupTouchZones() end function ReaderHighlight:addToMainMenu(tab_item_table) @@ -109,13 +109,6 @@ function ReaderHighlight:genHighlightDrawerMenu() } end -function ReaderHighlight:onSetDimensions(dimen) - -- update listening according to new screen dimen - if Device:isTouchDevice() then - self:initGesListener() - end -end - function ReaderHighlight:clear() if self.ui.document.info.has_pages then self.view.highlight.temp = {} @@ -161,15 +154,16 @@ function ReaderHighlight:onTapPageSavedHighlight(ges) local pos = self.view:screenToPageTransform(ges.pos) for key, page in pairs(pages) do local items = self.view.highlight.saved[page] - if not items then items = {} end - for i = 1, #items do - local pos0, pos1 = items[i].pos0, items[i].pos1 - local boxes = self.ui.document:getPageBoxesFromPositions(page, pos0, pos1) - if boxes then - for index, box in pairs(boxes) do - if inside_box(pos, box) then - logger.dbg("Tap on hightlight") - return self:onShowHighlightDialog(page, i) + if items then + for i = 1, #items do + local pos0, pos1 = items[i].pos0, items[i].pos1 + local boxes = self.ui.document:getPageBoxesFromPositions(page, pos0, pos1) + if boxes then + for index, box in pairs(boxes) do + if inside_box(pos, box) then + logger.dbg("Tap on hightlight") + return self:onShowHighlightDialog(page, i) + end end end end @@ -181,15 +175,16 @@ function ReaderHighlight:onTapXPointerSavedHighlight(ges) local pos = self.view:screenToPageTransform(ges.pos) for page, _ in pairs(self.view.highlight.saved) do local items = self.view.highlight.saved[page] - if not items then items = {} end - for i = 1, #items do - local pos0, pos1 = items[i].pos0, items[i].pos1 - local boxes = self.ui.document:getScreenBoxesFromPositions(pos0, pos1) - if boxes then - for index, box in pairs(boxes) do - if inside_box(pos, box) then - logger.dbg("Tap on hightlight") - return self:onShowHighlightDialog(page, i) + if items then + for i = 1, #items do + local pos0, pos1 = items[i].pos0, items[i].pos1 + local boxes = self.ui.document:getScreenBoxesFromPositions(pos0, pos1) + if boxes then + for index, box in pairs(boxes) do + if inside_box(pos, box) then + logger.dbg("Tap on hightlight") + return self:onShowHighlightDialog(page, i) + end end end end diff --git a/frontend/apps/reader/modules/readermenu.lua b/frontend/apps/reader/modules/readermenu.lua index 59e548ae6..1cc161794 100644 --- a/frontend/apps/reader/modules/readermenu.lua +++ b/frontend/apps/reader/modules/readermenu.lua @@ -89,6 +89,7 @@ function ReaderMenu:onReaderReady() ratio_x = DTAP_ZONE_MENU.x, ratio_y = DTAP_ZONE_MENU.y, ratio_w = DTAP_ZONE_MENU.w, ratio_h = DTAP_ZONE_MENU.h, }, + overrides = { "tap_forward", "tap_backward", }, handler = function() return self:onTapShowMenu() end, }, }) diff --git a/frontend/depgraph.lua b/frontend/depgraph.lua new file mode 100644 index 000000000..2a1d3862e --- /dev/null +++ b/frontend/depgraph.lua @@ -0,0 +1,106 @@ +--[[-- +DepGraph module. +Library for constructing dependency graphs. + +Example: + + local dg = DepGraph:new{} + dg:addNode('a1', {'a2', 'b1'}) + dg:addNode('b1', {'a2', 'c1'}) + dg:addNode('c1') + -- The return value of dg:serialize() will be: + -- {'a2', 'c1', 'b1', 'a1'} + +]] + +local DepGraph = {} + +function DepGraph:new(new_o) + local o = new_o or {} + o.nodes = {} + setmetatable(o, self) + self.__index = self + return o +end + +function DepGraph:addNode(node_key, deps) + if not self.nodes[node_key] then + self.nodes[node_key] = {} + end + if not deps then return end + + local node_deps = {} + for _,dep_node_key in ipairs(deps) do + if not self.nodes[dep_node_key] then + self.nodes[dep_node_key] = {} + end + table.insert(node_deps, dep_node_key) + end + self.nodes[node_key].deps = node_deps +end + +function DepGraph:removeNode(node_key) + self.nodes[node_key] = nil + for curr_node_key, curr_node in pairs(self.nodes) do + if curr_node.deps then + local remove_idx + for idx, dep_node_key in ipairs(self.nodes) do + if dep_node_key == node_key then + remove_idx = idx + break + end + end + if remove_idx then table.remove(curr_node.deps, remove_idx) end + end + end +end + +function DepGraph:addNodeDep(node_key, dep_node_key) + local node = self.nodes[node_key] + if not node then + node = {} + self.nodes[node_key] = node + end + if not node.deps then node.deps = {} end + table.insert(node.deps, dep_node_key) +end + +function DepGraph:serialize() + local visited = {} + local ordered_nodes = {} + for node_key,_ in pairs(self.nodes) do + if not visited[node_key] then + local queue = {node_key} + while #queue > 0 do + local pos = #queue + local curr_node_key = queue[pos] + local curr_node = self.nodes[curr_node_key] + local all_deps_visited = true + if curr_node.deps then + for _, dep_node_key in ipairs(curr_node.deps) do + if not visited[dep_node_key] then + -- only insert to queue for later process if node + -- has dependencies + if self.nodes[dep_node_key].deps then + table.insert(queue, dep_node_key) + else + table.insert(ordered_nodes, dep_node_key) + end + visited[dep_node_key] = true + all_deps_visited = false + break + end + end + end + if all_deps_visited then + visited[curr_node_key] = true + table.remove(queue, pos) + table.insert(ordered_nodes, curr_node_key) + end + end + end + end + return ordered_nodes +end + +return DepGraph diff --git a/frontend/ui/widget/container/inputcontainer.lua b/frontend/ui/widget/container/inputcontainer.lua index 2ff39aa38..0f5c28669 100644 --- a/frontend/ui/widget/container/inputcontainer.lua +++ b/frontend/ui/widget/container/inputcontainer.lua @@ -28,6 +28,7 @@ local WidgetContainer = require("ui/widget/container/widgetcontainer") local GestureRange = require("ui/gesturerange") local UIManager = require("ui/uimanager") local Screen = require("device").screen +local DepGraph = require("depgraph") local Geom = require("ui/geometry") local Event = require("ui/event") local _ = require("gettext") @@ -57,8 +58,9 @@ function InputContainer:_init() end end self.ges_events = new_ges_events - self._touch_zones = {} - self._touch_zone_pos_idx = {} + self.touch_zone_dg = nil + self._zones = {} + self._ordered_touch_zones = {} end function InputContainer:paintTo(bb, x, y) @@ -125,12 +127,13 @@ require("ui/uimanager"):show(test_widget) ]] function InputContainer:registerTouchZones(zones) local screen_width, screen_height = Screen:getWidth(), Screen:getHeight() + if not self.touch_zone_dg then self.touch_zone_dg = DepGraph:new{} end for _, zone in ipairs(zones) do - if self._touch_zone_pos_idx[zone.id] then - table.remove(self._touch_zones, self._touch_zone_pos_idx[zone.id]) - self._touch_zone_pos_idx[zone.id] = nil + -- override touch zone with the same id to support reregistration + if self._zones[zone.id] then + self.touch_zone_dg:removeNode(zone.id) end - local tzone = { + self._zones[zone.id]= { def = zone, handler = zone.handler, gs_range = GestureRange:new{ @@ -144,26 +147,16 @@ function InputContainer:registerTouchZones(zones) }, }, } - local insert_pos = #self._touch_zones - if insert_pos ~= 0 then - if zone.overrides then - for _, override_id in ipairs(zone.overrides) do - local zone_idx = self._touch_zone_pos_idx[override_id] - if zone_idx and zone_idx < insert_pos then - insert_pos = zone_idx - end - end - else - insert_pos = 0 + self.touch_zone_dg:addNode(zone.id) + if zone.overrides then + for _, override_zone_id in ipairs(zone.overrides) do + self.touch_zone_dg:addNodeDep(override_zone_id, zone.id) end end - if insert_pos == 0 then - table.insert(self._touch_zones, tzone) - self._touch_zone_pos_idx[zone.id] = 1 - else - table.insert(self._touch_zones, insert_pos, tzone) - self._touch_zone_pos_idx[zone.id] = insert_pos - end + end + self._ordered_touch_zones = {} + for _, zone_id in ipairs(self.touch_zone_dg:serialize()) do + table.insert(self._ordered_touch_zones, self._zones[zone_id]) end end @@ -173,7 +166,7 @@ Updates touch zones based on new screen dimensions. @tparam ui.geometry.Geom new_screen_dimen new screen dimensions ]] function InputContainer:updateTouchZonesOnScreenResize(new_screen_dimen) - for _, tzone in ipairs(self._touch_zones) do + for _, tzone in ipairs(self._ordered_touch_zones) do local range = tzone.gs_range.range range.x = new_screen_dimen.w * tzone.def.screen_zone.ratio_x range.y = new_screen_dimen.h * tzone.def.screen_zone.ratio_y @@ -200,16 +193,18 @@ function InputContainer:onKeyPress(key) end function InputContainer:onGesture(ev) - for _, tzone in ipairs(self._touch_zones) do - if tzone.gs_range:match(ev) then - return tzone.handler(ev) + for _, tzone in ipairs(self._ordered_touch_zones) do + if tzone.gs_range:match(ev) and tzone.handler(ev) then + return true end end for name, gsseq in pairs(self.ges_events) do for _, gs_range in ipairs(gsseq) do if gs_range:match(ev) then local eventname = gsseq.event or name - return self:handleEvent(Event:new(eventname, gsseq.args, ev)) + if self:handleEvent(Event:new(eventname, gsseq.args, ev)) then + return true + end end end end diff --git a/spec/unit/commonrequire.lua b/spec/unit/commonrequire.lua index 7e70d6991..f04f1b8a5 100644 --- a/spec/unit/commonrequire.lua +++ b/spec/unit/commonrequire.lua @@ -2,6 +2,11 @@ require "defaults" package.path = "?.lua;common/?.lua;rocks/share/lua/5.1/?.lua;frontend/?.lua;" .. package.path package.cpath = "?.so;common/?.so;/usr/lib/lua/?.so;rocks/lib/lua/5.1/?.so;" .. package.cpath +-- turn off debug by default and set log level to warning +require("dbg"):turnOff() +local logger = require("logger") +logger:setLevel(logger.levels.warn) + -- global reader settings local DataStorage = require("datastorage") os.remove(DataStorage:getDataDir().."/settings.reader.lua") @@ -20,9 +25,6 @@ Screen:init() local Input = require("device").input Input.dummy = true --- turn on debug ---require("dbg"):turnOn() - function assertAlmostEquals(expected, actual, margin) if type(actual) ~= 'number' or type(expected) ~= 'number' or type(margin) ~= 'number' then diff --git a/spec/unit/depgraph_spec.lua b/spec/unit/depgraph_spec.lua new file mode 100644 index 000000000..932847e49 --- /dev/null +++ b/spec/unit/depgraph_spec.lua @@ -0,0 +1,102 @@ +describe("DepGraph module", function() + local DepGraph, logger + setup(function() + require("commonrequire") + logger = require("logger") + DepGraph = require("depgraph") + end) + + it("should serialize simple graph", function() + local dg = DepGraph:new{} + dg:addNode('a1', {'a2', 'b1'}) + dg:addNode('b1', {'a2', 'c1'}) + dg:addNode('c1') + assert.are.same({'a2', 'c1', 'b1', 'a1'}, dg:serialize()) + end) + + it("should serialize complex graph", function() + local dg = DepGraph:new{} + dg:addNode('readerfooter_tap') + dg:addNode('readerfooter_hold', {}) + dg:addNode('readerhighlight_tap', {'tap_backward', 'tap_forward'}) + dg:addNode('tap_backward', {'readerfooter_tap', 'readermenu_tap'}) + dg:addNode('tap_forward', {'readerfooter_tap', 'readermenu_tap'}) + dg:addNode('readerhighlight_hold', {'readerfooter_hold'}) + dg:addNode('readerhighlight_hold_release', {}) + dg:addNode('readerhighlight_hold_pan', {}) + dg:addNode('readermenu_tap', {'readerfooter_tap'}) + dg:addNode('paging_swipe', {}) + dg:addNode('paging_pan', {}) + dg:addNode('paging_pan_release', {}) + assert.are.same({ + 'readerfooter_tap', + 'readermenu_tap', + 'tap_backward', + 'readerhighlight_hold_pan', + 'paging_pan_release', + 'readerfooter_hold', + 'readerhighlight_hold', + 'paging_pan', + 'paging_swipe', + 'tap_forward', + 'readerhighlight_tap', + 'readerhighlight_hold_release', + }, dg:serialize()) + end) + + it("should serialize complex graph2", function() + local dg = DepGraph:new{} + dg:addNode('readerfooter_tap') + dg:addNode('readerfooter_hold', {}) + dg:addNode('readerhighlight_tap', {}) + dg:addNode('tap_backward', {'readerfooter_tap', 'readermenu_tap', 'readerhighlight_tap'}) + dg:addNode('tap_forward', {'readerfooter_tap', 'readermenu_tap', 'readerhighlight_tap'}) + dg:addNode('readerhighlight_hold', {'readerfooter_hold'}) + dg:addNode('readerhighlight_hold_release', {}) + dg:addNode('readerhighlight_hold_pan', {}) + dg:addNode('readermenu_tap', {'readerfooter_tap'}) + assert.are.same({ + 'readerfooter_tap', + 'readermenu_tap', + 'readerhighlight_tap', + 'tap_backward', + 'readerhighlight_hold_pan', + 'readerfooter_hold', + 'readerhighlight_hold', + 'tap_forward', + 'readerhighlight_hold_release', + }, dg:serialize()) + end) + + + it("should serialize complex graph with duplicates", function() + local dg = DepGraph:new{} + dg:addNode('readerfooter_tap', {}) + dg:addNode('readerfooter_hold', {}) + dg:addNode('readerhighlight_tap', + {'tap_backward', 'tap_backward', 'tap_forward'}) + dg:addNode('tap_backward', {'readerfooter_tap', 'readermenu_tap'}) + dg:addNode('tap_forward', {'readerfooter_tap', 'readermenu_tap'}) + dg:addNode('readerhighlight_hold', {'readerfooter_hold'}) + dg:addNode('readerhighlight_hold_release', {}) + dg:addNode('readerhighlight_hold_pan', {}) + dg:addNode('readermenu_tap', {'readerfooter_tap'}) + dg:addNode('paging_swipe', {}) + dg:addNode('paging_pan', {}) + dg:addNode('paging_pan_release', {}) + assert.are.same({ + 'readerfooter_tap', + 'readermenu_tap', + 'tap_backward', + 'readerhighlight_hold_pan', + 'paging_pan_release', + 'readerfooter_hold', + 'readerhighlight_hold', + 'paging_pan', + 'paging_swipe', + 'tap_forward', + 'readerhighlight_tap', + 'readerhighlight_hold_release', + }, dg:serialize()) + end) +end) diff --git a/spec/unit/widget_inputcontainer_spec.lua b/spec/unit/widget_inputcontainer_spec.lua index e0677a826..c907f90a4 100644 --- a/spec/unit/widget_inputcontainer_spec.lua +++ b/spec/unit/widget_inputcontainer_spec.lua @@ -8,7 +8,7 @@ describe("InputContainer widget", function() it("should register touch zones", function() local ic = InputContainer:new{} - assert.is.same(#ic._touch_zones, 0) + assert.is.same(#ic._ordered_touch_zones, 0) ic:registerTouchZones({ { @@ -30,15 +30,15 @@ describe("InputContainer widget", function() }) local screen_width, screen_height = Screen:getWidth(), Screen:getHeight() - assert.is.same(#ic._touch_zones, 2) - assert.is.same("foo", ic._touch_zones[1].def.id) - assert.is.same(ic._touch_zones[1].def.handler, ic._touch_zones[1].handler) - assert.is.same("bar", ic._touch_zones[2].def.id) - assert.is.same("tap", ic._touch_zones[2].gs_range.ges) - assert.is.same(0, ic._touch_zones[2].gs_range.range.x) - assert.is.same(screen_height * 0.1, ic._touch_zones[2].gs_range.range.y) - assert.is.same(screen_width / 2, ic._touch_zones[2].gs_range.range.w) - assert.is.same(screen_height, ic._touch_zones[2].gs_range.range.h) + assert.is.same(#ic._ordered_touch_zones, 2) + assert.is.same("foo", ic._ordered_touch_zones[1].def.id) + assert.is.same(ic._ordered_touch_zones[1].def.handler, ic._ordered_touch_zones[1].handler) + assert.is.same("bar", ic._ordered_touch_zones[2].def.id) + assert.is.same("tap", ic._ordered_touch_zones[2].gs_range.ges) + assert.is.same(0, ic._ordered_touch_zones[2].gs_range.range.x) + assert.is.same(screen_height * 0.1, ic._ordered_touch_zones[2].gs_range.range.y) + assert.is.same(screen_width / 2, ic._ordered_touch_zones[2].gs_range.range.w) + assert.is.same(screen_height, ic._ordered_touch_zones[2].gs_range.range.h) end) it("should support overrides for touch zones", function() @@ -70,8 +70,86 @@ describe("InputContainer widget", function() handler = function() end, }, }) - assert.is.same(ic._touch_zones[1].def.id, 'baz') - assert.is.same(ic._touch_zones[2].def.id, 'foo') - assert.is.same(ic._touch_zones[3].def.id, 'bar') + assert.is.same(ic._ordered_touch_zones[1].def.id, 'baz') + assert.is.same(ic._ordered_touch_zones[2].def.id, 'foo') + assert.is.same(ic._ordered_touch_zones[3].def.id, 'bar') + end) + + it("should support indirect overrides for touch zones", function() + local ic = InputContainer:new{} + local dummy_screen_size = {ratio_x = 0, ratio_y = 0, ratio_w = 1, ratio_h = 1,} + ic:registerTouchZones({ + { + id = "readerfooter_tap", + ges = "tap", + screen_zone = dummy_screen_size, + overrides = { + 'tap_forward', 'tap_backward', 'readermenu_tap', + }, + handler = function() end, + }, + { + id = "readerfooter_hold", + ges = "hold", + screen_zone = dummy_screen_size, + overrides = {'readerhighlight_hold'}, + handler = function() end, + }, + { + id = "readerhighlight_tap", + ges = "tap", + screen_zone = dummy_screen_size, + overrides = { 'tap_forward', 'tap_backward', }, + handler = function() end, + }, + { + id = "readerhighlight_hold", + ges = "hold", + screen_zone = dummy_screen_size, + handler = function() end, + }, + { + id = "readerhighlight_hold_release", + ges = "hold_release", + screen_zone = dummy_screen_size, + handler = function() end, + }, + { + id = "readerhighlight_hold_pan", + ges = "hold_pan", + rate = 2.0, + screen_zone = dummy_screen_size, + handler = function() end, + }, + { + id = "tap_forward", + ges = "tap", + screen_zone = dummy_screen_size, + handler = function() end, + }, + { + id = "tap_backward", + ges = "tap", + screen_zone = dummy_screen_size, + handler = function() end, + }, + { + id = "readermenu_tap", + ges = "tap", + overrides = { "tap_forward", "tap_backward", }, + screen_zone = dummy_screen_size, + handler = function() end, + }, + }) + + assert.is.same('readerfooter_tap', ic._ordered_touch_zones[1].def.id) + assert.is.same('readerhighlight_tap', ic._ordered_touch_zones[2].def.id) + assert.is.same('readermenu_tap', ic._ordered_touch_zones[3].def.id) + assert.is.same('tap_backward', ic._ordered_touch_zones[4].def.id) + assert.is.same('readerhighlight_hold_pan', ic._ordered_touch_zones[5].def.id) + assert.is.same('readerfooter_hold', ic._ordered_touch_zones[6].def.id) + assert.is.same('readerhighlight_hold', ic._ordered_touch_zones[7].def.id) + assert.is.same('tap_forward', ic._ordered_touch_zones[8].def.id) + assert.is.same('readerhighlight_hold_release', ic._ordered_touch_zones[9].def.id) end) end)