From 0c49b915de69b259a7f40eb04affd8e3a12e3eb7 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sat, 3 Dec 2016 22:57:57 -0800 Subject: [PATCH] refactor: add touch zone subsystem to inputcontainer Touch zone decouples screen size from gesture event registration. The win here is each individual widget does not need to update gesture range on screen rotate/resize anymore. Another advantage is we now have a centralized ordered array to handle all registered touch event listeners, makes it much easier to resolve gesture range conflicts between multiple widgets. This patch also includes the following changes: * migrate readerpaging to use readerui's touch zone * migrate readerfooter to use readerui's touch zone * move inverse read direction setting to touch menu's setting tab * moved kobolight widget from readerview into readerui * various dead code cleanups and comments --- .../apps/reader/modules/readercropping.lua | 1 + frontend/apps/reader/modules/readerfooter.lua | 97 ++++----- frontend/apps/reader/modules/readerpaging.lua | 192 +++++++----------- frontend/apps/reader/modules/readerview.lua | 12 +- frontend/apps/reader/readerui.lua | 13 +- frontend/device/gesturedetector.lua | 3 +- frontend/ui/geometry.lua | 7 +- .../ui/widget/container/inputcontainer.lua | 144 +++++++++++-- spec/unit/widget_inputcontainer_spec.lua | 77 +++++++ 9 files changed, 350 insertions(+), 196 deletions(-) create mode 100644 spec/unit/widget_inputcontainer_spec.lua diff --git a/frontend/apps/reader/modules/readercropping.lua b/frontend/apps/reader/modules/readercropping.lua index 9e44df430..23015f32e 100644 --- a/frontend/apps/reader/modules/readercropping.lua +++ b/frontend/apps/reader/modules/readercropping.lua @@ -77,6 +77,7 @@ function PageCropDialog:onShow() return true end + local ReaderCropping = InputContainer:new{} function ReaderCropping:onPageCrop(mode) diff --git a/frontend/apps/reader/modules/readerfooter.lua b/frontend/apps/reader/modules/readerfooter.lua index 356dcd5d7..e53084efc 100644 --- a/frontend/apps/reader/modules/readerfooter.lua +++ b/frontend/apps/reader/modules/readerfooter.lua @@ -1,4 +1,4 @@ -local InputContainer = require("ui/widget/container/inputcontainer") +local WidgetContainer = require("ui/widget/container/widgetcontainer") local RightContainer = require("ui/widget/container/rightcontainer") local BottomContainer = require("ui/widget/container/bottomcontainer") local FrameContainer = require("ui/widget/container/framecontainer") @@ -6,7 +6,6 @@ local ProgressWidget = require("ui/widget/progresswidget") local HorizontalGroup = require("ui/widget/horizontalgroup") local HorizontalSpan = require("ui/widget/horizontalspan") local TextWidget = require("ui/widget/textwidget") -local GestureRange = require("ui/gesturerange") local Blitbuffer = require("ffi/blitbuffer") local UIManager = require("ui/uimanager") local Device = require("device") @@ -88,7 +87,7 @@ local footerTextGeneratorMap = { end, } -local ReaderFooter = InputContainer:new{ +local ReaderFooter = WidgetContainer:new{ mode = MODE.page_progress, pageno = nil, pages = nil, @@ -152,27 +151,31 @@ function ReaderFooter:init() last = nil, -- last will be initialized in self:updateFooterText } - local margin_span = HorizontalSpan:new{width = self.horizontal_margin} - self.horizontal_group = HorizontalGroup:new{margin_span} + local margin_span = HorizontalSpan:new{ width = self.horizontal_margin } + self.horizontal_group = HorizontalGroup:new{ margin_span } self.text_container = RightContainer:new{ - dimen = Geom:new{w = 0, h = self.height}, + dimen = Geom:new{ w = 0, h = self.height }, self.footer_text, } table.insert(self.horizontal_group, self.progress_bar) table.insert(self.horizontal_group, self.text_container) table.insert(self.horizontal_group, margin_span) - self[1] = BottomContainer:new{ + + self.footer_content = FrameContainer:new{ + self.horizontal_group, + background = Blitbuffer.COLOR_WHITE, + bordersize = 0, + padding = 0, + } + self.footer_container = BottomContainer:new{ + dimen = Geom:new{ w = 0, h = self.height*2 }, + self.footer_content, + } + self.footer_positioner = BottomContainer:new{ dimen = Geom:new{}, - BottomContainer:new{ - dimen = Geom:new{w = 0, h = self.height*2}, - FrameContainer:new{ - self.horizontal_group, - background = Blitbuffer.COLOR_WHITE, - bordersize = 0, - padding = 0, - } - } + self.footer_container, } + self[1] = self.footer_positioner self.mode = G_reader_settings:readSetting("reader_footer_mode") or self.mode if self.settings.all_at_once then @@ -181,7 +184,6 @@ function ReaderFooter:init() else self:applyFooterMode() end - self:resetLayout() if self.settings.auto_refresh_time then self:setupAutoRefreshTime() @@ -201,7 +203,30 @@ function ReaderFooter:setupAutoRefreshTime() UIManager:scheduleIn(61 - tonumber(os.date("%S")), self.autoRefreshTime) end --- call this method whenever the screen size changed +function ReaderFooter:setupTouchZones() + if not Device:isTouchDevice() then return end + local footer_screen_zone = { + ratio_x = DTAP_ZONE_MINIBAR.x, ratio_y = DTAP_ZONE_MINIBAR.y, + ratio_w = DTAP_ZONE_MINIBAR.w, ratio_h = DTAP_ZONE_MINIBAR.h, + } + self.ui:registerTouchZones({ + { + id = "footer_tap", + ges = "tap", + screen_zone = footer_screen_zone, + handler = function() return self:onTapFooter() end, + overrides = { 'tap_forward', 'tap_backward', }, + }, + { + id = "footer_hold", + ges = "hold", + screen_zone = footer_screen_zone, + handler = function() return self:onHoldFooter() end, + }, + }) +end + +-- call this method whenever the screen size changes function ReaderFooter:resetLayout() local new_screen_width = Screen:getWidth() if new_screen_width == self._saved_screen_width then return end @@ -209,34 +234,12 @@ function ReaderFooter:resetLayout() self.progress_bar.width = math.floor(new_screen_width - self.text_width - self.horizontal_margin*2) self.horizontal_group:resetLayout() - self[1].dimen.w = new_screen_width - self[1].dimen.h = new_screen_height - self[1][1].dimen.w = new_screen_width - self.dimen = self[1]:getSize() + self.footer_positioner.dimen.w = new_screen_width + self.footer_positioner.dimen.h = new_screen_height + self.footer_container.dimen.w = new_screen_width + self.dimen = self.footer_positioner:getSize() self._saved_screen_width = new_screen_width - if Device:isTouchDevice() then - local range = Geom:new{ - x = new_screen_width*DTAP_ZONE_MINIBAR.x, - y = new_screen_height*DTAP_ZONE_MINIBAR.y, - w = new_screen_width*DTAP_ZONE_MINIBAR.w, - h = new_screen_height*DTAP_ZONE_MINIBAR.h - } - self.ges_events = { - TapFooter = { - GestureRange:new{ - ges = "tap", - range = range, - }, - }, - HoldFooter = { - GestureRange:new{ - ges = "hold", - range = range, - }, - }, - } - end end function ReaderFooter:getHeight() @@ -481,7 +484,7 @@ function ReaderFooter:_updateFooterText() self._saved_screen_width - self.text_width - self.horizontal_margin*2) self.text_container.dimen.w = self.text_width self.horizontal_group:resetLayout() - UIManager:setDirty(self.view.dialog, "ui", self[1][1][1].dimen) + UIManager:setDirty(self.view.dialog, "ui", self.footer_content.dimen) end function ReaderFooter:onPageUpdate(pageno) @@ -501,6 +504,8 @@ end ReaderFooter.onUpdatePos = ReaderFooter.updateFooter function ReaderFooter:onReaderReady() + self:setupTouchZones() + self:resetLayout() -- set widget dimen self:setTocMarkers() self.updateFooterText = self._updateFooterText self:updateFooter() @@ -574,7 +579,7 @@ function ReaderFooter:onTapFooter(arg, ges) return true end -function ReaderFooter:onHoldFooter(arg, ges) +function ReaderFooter:onHoldFooter() if self.mode == MODE.off then return end self.ui:handleEvent(Event:new("ShowGotoDialog")) return true diff --git a/frontend/apps/reader/modules/readerpaging.lua b/frontend/apps/reader/modules/readerpaging.lua index 8e012adee..425ccc822 100644 --- a/frontend/apps/reader/modules/readerpaging.lua +++ b/frontend/apps/reader/modules/readerpaging.lua @@ -1,7 +1,6 @@ local InputContainer = require("ui/widget/container/inputcontainer") local Geom = require("ui/geometry") local Input = require("device").input -local GestureRange = require("ui/gesturerange") local Device = require("device") local Screen = Device.screen local Event = require("ui/event") @@ -11,6 +10,9 @@ local DEBUG = require("dbg") local _ = require("gettext") +local pan_rate = Screen.eink and 4.0 or 10.0 + + local function copyPageState(page_state) return { page = page_state.page, @@ -75,64 +77,77 @@ function ReaderPaging:init() self.ui.menu:registerToMainMenu(self) end --- This method will be called in onSetDimensions handler -function ReaderPaging:initGesListener() - self.ges_events = { - TapForward = { - GestureRange:new{ - ges = "tap", - range = Geom:new{ - x = Screen:getWidth()*DTAP_ZONE_FORWARD.x, - y = Screen:getHeight()*DTAP_ZONE_FORWARD.y, - w = Screen:getWidth()*DTAP_ZONE_FORWARD.w, - h = Screen:getHeight()*DTAP_ZONE_FORWARD.h, - } - } +function ReaderPaging:onReaderReady() + self:setupTouchZones() +end + +function ReaderPaging:setupTapTouchZones() + local forward_zone = { + ratio_x = DTAP_ZONE_FORWARD.x, ratio_y = DTAP_ZONE_FORWARD.y, + ratio_w = DTAP_ZONE_FORWARD.w, ratio_h = DTAP_ZONE_FORWARD.h, + } + local backward_zone = { + ratio_x = DTAP_ZONE_BACKWARD.x, ratio_y = DTAP_ZONE_BACKWARD.y, + ratio_w = DTAP_ZONE_BACKWARD.w, ratio_h = DTAP_ZONE_BACKWARD.h, + } + + if self.inverse_reading_order then + forward_zone.ratio_x = 1 - forward_zone.ratio_x - forward_zone.ratio_w + backward_zone.ratio_x = 1 - backward_zone.ratio_x - backward_zone.ratio_w + end + + self.ui:registerTouchZones({ + { + id = "tap_forward", + ges = "tap", + screen_zone = forward_zone, + handler = function() return self:onTapForward() end }, - TapBackward = { - GestureRange:new{ - ges = "tap", - range = Geom:new{ - x = Screen:getWidth()*DTAP_ZONE_BACKWARD.x, - y = Screen:getHeight()*DTAP_ZONE_BACKWARD.y, - w = Screen:getWidth()*DTAP_ZONE_BACKWARD.w, - h = Screen:getHeight()*DTAP_ZONE_BACKWARD.h, - } - } + { + id = "tap_backward", + ges = "tap", + screen_zone = backward_zone, + handler = function() return self:onTapBackward() end }, - Swipe = { - GestureRange:new{ - ges = "swipe", - range = Geom:new{ - x = 0, y = 0, - w = Screen:getWidth(), - h = Screen:getHeight(), - } - } + }) +end + +-- This method will be called in onSetDimensions handler +function ReaderPaging:setupTouchZones() + -- deligate gesture listener to readerui + self.ges_events = {} + self.onGesture = nil + + if not Device:isTouchDevice() then return end + + self:setupTapTouchZones() + self.ui:registerTouchZones({ + { + id = "paging_swipe", + ges = "swipe", + screen_zone = { + ratio_x = 0, ratio_y = 0, ratio_w = 1, ratio_h = 1, + }, + handler = function(ges) return self:onSwipe(nil, ges) end }, - Pan = { - GestureRange:new{ - ges = "pan", - range = Geom:new{ - x = 0, y = 0, - w = Screen:getWidth(), - h = Screen:getHeight(), - }, - rate = Screen.eink and 4.0 or 10.0, - } + { + id = "paging_pan", + ges = "pan", + rate = pan_rate, + screen_zone = { + ratio_x = 0, ratio_y = 0, ratio_w = 1, ratio_h = 1, + }, + handler = function(ges) return self:onPan(nil, ges) end }, - PanRelease = { - GestureRange:new{ - ges = "pan_release", - range = Geom:new{ - x = 0, y = 0, - w = Screen:getWidth(), - h = Screen:getHeight(), - }, - } + { + id = "paging_pan_release", + ges = "pan_release", + screen_zone = { + ratio_x = 0, ratio_y = 0, ratio_w = 1, ratio_h = 1, + }, + handler = function(ges) return self:onPanRelease(nil, ges) end }, - } - self:updateReadOrder() + }) end function ReaderPaging:onReadSettings(config) @@ -145,7 +160,6 @@ function ReaderPaging:onReadSettings(config) self.flipping_zoom_mode = config:readSetting("flipping_zoom_mode") or "page" self.flipping_scroll_mode = config:readSetting("flipping_scroll_mode") or false self.inverse_reading_order = config:readSetting("inverse_reading_order") or false - self:updateReadOrder() end function ReaderPaging:onSaveSettings() @@ -197,12 +211,12 @@ function ReaderPaging:addToMainMenu(tab_item_table) end, sub_item_table = page_overlap_menu, }) - table.insert(tab_item_table.typeset, { + table.insert(tab_item_table.setting, { text = _("Read from right to left"), checked_func = function() return self.inverse_reading_order end, callback = function() self.inverse_reading_order = not self.inverse_reading_order - self:updateReadOrder() + self:setupTapTouchZones() end, }) end @@ -335,7 +349,7 @@ function ReaderPaging:bookmarkFlipping(flipping_page, flipping_ges) UIManager:setDirty(self.view.dialog, "partial") end -function ReaderPaging:onSwipe(arg, ges) +function ReaderPaging:onSwipe(_, ges) if self.bookmark_flipping_mode then self:bookmarkFlipping(self.current_page, ges) elseif self.page_flipping_mode and self.original_page then @@ -358,7 +372,7 @@ function ReaderPaging:onSwipe(arg, ges) end end -function ReaderPaging:onPan(arg, ges) +function ReaderPaging:onPan(_, ges) if self.bookmark_flipping_mode then return true elseif self.page_flipping_mode then @@ -374,7 +388,7 @@ function ReaderPaging:onPan(arg, ges) return true end -function ReaderPaging:onPanRelease(arg, ges) +function ReaderPaging:onPanRelease(_, ges) if self.page_flipping_mode then if self.view.zoom_mode == "page" then self:updateFlippingPage(self.current_page) @@ -832,13 +846,6 @@ function ReaderPaging:onRedrawCurrentPage() return true end -function ReaderPaging:onSetDimensions() - -- update listening according to new screen dimen - if Device:isTouchDevice() then - self:initGesListener() - end -end - -- wrapper for bounds checking function ReaderPaging:_gotoPage(number, orig_mode) if number == self.current_page or not number then @@ -870,55 +877,4 @@ function ReaderPaging:onGotoPercentage(percentage) return true end -function ReaderPaging:updateReadOrder() - local width, height = Screen:getWidth(), Screen:getHeight() - if self.inverse_reading_order then - self.ges_events.TapForward = { - GestureRange:new{ - ges = "tap", - range = Geom:new{ - x = width * (1-DTAP_ZONE_FORWARD.x - DTAP_ZONE_FORWARD.w), - y = height * DTAP_ZONE_FORWARD.y, - w = width * DTAP_ZONE_FORWARD.w, - h = height * DTAP_ZONE_FORWARD.h, - } - } - } - self.ges_events.TapBackward = { - GestureRange:new{ - ges = "tap", - range = Geom:new{ - x = width * (1-DTAP_ZONE_BACKWARD.x - DTAP_ZONE_BACKWARD.w), - y = height * DTAP_ZONE_BACKWARD.y, - w = width * DTAP_ZONE_BACKWARD.w, - h = height * DTAP_ZONE_BACKWARD.h, - } - } - } - else - self.ges_events.TapForward = { - GestureRange:new{ - ges = "tap", - range = Geom:new{ - x = width * DTAP_ZONE_FORWARD.x, - y = height * DTAP_ZONE_FORWARD.y, - w = width * DTAP_ZONE_FORWARD.w, - h = height * DTAP_ZONE_FORWARD.h, - } - } - } - self.ges_events.TapBackward = { - GestureRange:new{ - ges = "tap", - range = Geom:new{ - x = width * DTAP_ZONE_BACKWARD.x, - y = height * DTAP_ZONE_BACKWARD.y, - w = width * DTAP_ZONE_BACKWARD.w, - h = height * DTAP_ZONE_BACKWARD.h, - } - } - } - end -end - return ReaderPaging diff --git a/frontend/apps/reader/modules/readerview.lua b/frontend/apps/reader/modules/readerview.lua index 015e23c6a..f2627c292 100644 --- a/frontend/apps/reader/modules/readerview.lua +++ b/frontend/apps/reader/modules/readerview.lua @@ -12,7 +12,6 @@ local Event = require("ui/event") local dbg = require("dbg") local Blitbuffer = require("ffi/blitbuffer") local _ = require("gettext") -local ReaderKoboLight = require("apps/reader/modules/readerkobolight") local ReaderView = OverlapGroup:new{ document = nil, @@ -115,13 +114,6 @@ function ReaderView:addWidgets() self[1] = self.dogear self[2] = self.footer self[3] = self.flipping - if (Device:isKobo() and Device:hasFrontlight()) then - self.kobolight = ReaderKoboLight:new{ - view = self, - ui = self.ui, - } - self[4] = self.kobolight - end end function ReaderView:resetLayout() @@ -609,7 +601,9 @@ function ReaderView:onSetScreenMode(new_mode, rotation) Screen:setScreenMode(new_mode) end UIManager:setDirty(self.dialog, "full") - self.ui:handleEvent(Event:new("SetDimensions", Screen:getSize())) + local new_screen_size = Screen:getSize() + self.ui:handleEvent(Event:new("SetDimensions", new_screen_size)) + self.ui:onScreenResize(new_screen_size) self.ui:handleEvent(Event:new("InitScrollPageStates")) end self.cur_rotation_mode = Screen.cur_rotation_mode diff --git a/frontend/apps/reader/readerui.lua b/frontend/apps/reader/readerui.lua index 46c2fa97d..a21140b3a 100644 --- a/frontend/apps/reader/readerui.lua +++ b/frontend/apps/reader/readerui.lua @@ -317,7 +317,15 @@ function ReaderUI:init() }) end - --dbg(self.doc_settings) + local ReaderKoboLight = require("apps/reader/modules/readerkobolight") + if (Device:isKobo() and Device:hasFrontlight()) then + self:registerModule('kobolight', ReaderKoboLight:new{ + dialog = self.dialog, + view = self.view, + ui = self, + }) + end + -- we only read settings after all the widgets are initialized self:handleEvent(Event:new("ReadSettings", self.doc_settings)) @@ -445,8 +453,9 @@ function ReaderUI:closeDialog() UIManager:close(self.password_dialog) end -function ReaderUI:onSetDimensions(dimen) +function ReaderUI:onScreenResize(dimen) self.dimen = dimen + self:updateTouchZonesOnScreenResize(dimen) end function ReaderUI:saveSettings() diff --git a/frontend/device/gesturedetector.lua b/frontend/device/gesturedetector.lua index a99273ed5..9ff212ff4 100644 --- a/frontend/device/gesturedetector.lua +++ b/frontend/device/gesturedetector.lua @@ -4,7 +4,8 @@ local DEBUG = require("dbg") --[[ Current detectable gestures: - * tap + * touch (user touched screen) + * tap (touch action detected as single tap) * pan * hold * swipe diff --git a/frontend/ui/geometry.lua b/frontend/ui/geometry.lua index 342cd654e..8b03fa286 100644 --- a/frontend/ui/geometry.lua +++ b/frontend/ui/geometry.lua @@ -265,9 +265,7 @@ Check size of dimension/rectangle for equality @tparam Geom rect_b ]] function Geom:equalSize(rect_b) - if self.w == rect_b.w - and self.h == rect_b.h - then + if self.w == rect_b.w and self.h == rect_b.h then return true end return false @@ -279,12 +277,9 @@ Check if our size is smaller than the size of the given dimension/rectangle @tparam Geom rect_b ]] function Geom:__lt(rect_b) - DEBUG("lt:",self,rect_b) if self.w < rect_b.w and self.h < rect_b.h then -DEBUG("lt+") return true end -DEBUG("lt-") return false end diff --git a/frontend/ui/widget/container/inputcontainer.lua b/frontend/ui/widget/container/inputcontainer.lua index e688d9603..6a472dea7 100644 --- a/frontend/ui/widget/container/inputcontainer.lua +++ b/frontend/ui/widget/container/inputcontainer.lua @@ -1,17 +1,10 @@ -local WidgetContainer = require("ui/widget/container/widgetcontainer") -local UIManager = require("ui/uimanager") -local Geom = require("ui/geometry") -local Event = require("ui/event") -local _ = require("gettext") +--[[-- +An InputContainer is an WidgetContainer that handles user input events including multi touches +and key presses. -if require("device"):isAndroid() then - require("jit").off(true, true) -end - ---[[ -an InputContainer is an WidgetContainer that handles input events +See @{InputContainer:registerTouchZones} for example on how to listen for multi touch inputs. -an example for a key_event is this: +An example for listening on key press input event is this: PanBy20 = { { "Shift", Input.group.Cursor }, @@ -26,9 +19,23 @@ an example for a key_event is this: }, Quit = { {"Home"} }, -it is suggested to reference configurable sequences from another table +It is suggested to reference configurable sequences from another table and store that table as configuration setting ---]] + +]] + +local WidgetContainer = require("ui/widget/container/widgetcontainer") +local GestureRange = require("ui/gesturerange") +local UIManager = require("ui/uimanager") +local Screen = require("device").screen +local Geom = require("ui/geometry") +local Event = require("ui/event") +local _ = require("gettext") + +if require("device"):isAndroid() then + require("jit").off(true, true) +end + local InputContainer = WidgetContainer:new{ vertical_align = "top", } @@ -50,6 +57,8 @@ function InputContainer:_init() end end self.ges_events = new_ges_events + self._touch_zones = {} + self._touch_zone_pos_idx = {} end function InputContainer:paintTo(bb, x, y) @@ -71,6 +80,108 @@ function InputContainer:paintTo(bb, x, y) end end +--[[-- + +Register touch zones into this InputContainer. + +See gesturedetector for list of supported gestures. + +NOTE: You are responsible for calling self:@{updateTouchZonesOnScreenResize} with the new +screen dimension whenever the screen is rotated or resized. + +@tparam table zones list of touch zones to register + +@usage +local InputContainer = require("ui/widget/container/inputcontainer") +local test_widget = InputContainer:new{} +test_widget:registerTouchZones({ + { + id = "foo_tap", + ges = "tap", + -- This binds handler to the full screen + screen_zone = { + ratio_x = 0, ratio_y = 0, ratio_w = 1, ratio_h = 1, + }, + handler = function(ges) + print('User tapped on screen!') + return true + end + }, + { + id = "foo_swipe", + ges = "swipe", + -- This binds handler to bottom half of the screen + screen_zone = { + ratio_x = 0, ratio_y = 0.5, ratio_w = 1, ratio_h = 0.5, + }, + handler = function(ges) + print("User swiped at the bottom with direction:", ges.direction) + return true + end + }, +}) +require("ui/uimanager"):show(test_widget) + +]] +function InputContainer:registerTouchZones(zones) + local screen_width, screen_height = Screen:getWidth(), Screen:getHeight() + 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 + end + local tzone = { + def = zone, + handler = zone.handler, + gs_range = GestureRange:new{ + ges = zone.ges, + rate = zone.rate, + range = Geom:new{ + x = screen_width * zone.screen_zone.ratio_x, + y = screen_height * zone.screen_zone.ratio_y, + w = screen_width * zone.screen_zone.ratio_w, + h = screen_height * zone.screen_zone.ratio_h, + }, + }, + } + 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 + 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 +end + +--[[-- +Update touch zones based on new screen dimension. + +@tparam ui.geometry.Geom new_screen_dimen new screen dimension +]] +function InputContainer:updateTouchZonesOnScreenResize(new_screen_dimen) + for _, tzone in ipairs(self._touch_zones) do + local range = tzone.gs_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 + range.w = new_screen_dimen.w * tzone.def.screen_zone.ratio_w + range.h = new_screen_dimen.h * tzone.def.screen_zone.ratio_h + end +end + --[[ the following handler handles keypresses and checks if they lead to a command. if this is the case, we retransmit another event within ourselves @@ -89,6 +200,11 @@ 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) + end + end for name, gsseq in pairs(self.ges_events) do for _, gs_range in ipairs(gsseq) do if gs_range:match(ev) then diff --git a/spec/unit/widget_inputcontainer_spec.lua b/spec/unit/widget_inputcontainer_spec.lua new file mode 100644 index 000000000..e0677a826 --- /dev/null +++ b/spec/unit/widget_inputcontainer_spec.lua @@ -0,0 +1,77 @@ +describe("InputContainer widget", function() + local InputContainer, Screen + setup(function() + require("commonrequire") + InputContainer = require("ui/widget/container/inputcontainer") + Screen = require("device").screen + end) + + it("should register touch zones", function() + local ic = InputContainer:new{} + assert.is.same(#ic._touch_zones, 0) + + ic:registerTouchZones({ + { + id = "foo", + ges = "swipe", + screen_zone = { + ratio_x = 0, ratio_y = 0, ratio_w = 1, ratio_h = 1, + }, + handler = function() end, + }, + { + id = "bar", + ges = "tap", + screen_zone = { + ratio_x = 0, ratio_y = 0.1, ratio_w = 0.5, ratio_h = 1, + }, + handler = function() end, + }, + }) + + 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) + end) + + it("should support overrides for touch zones", function() + local ic = InputContainer:new{} + ic:registerTouchZones({ + { + id = "foo", + ges = "tap", + screen_zone = { + ratio_x = 0, ratio_y = 0, ratio_w = 1, ratio_h = 1, + }, + handler = function() end, + }, + { + id = "bar", + ges = "tap", + screen_zone = { + ratio_x = 0, ratio_y = 0, ratio_w = 0.5, ratio_h = 1, + }, + handler = function() end, + }, + { + id = "baz", + ges = "tap", + screen_zone = { + ratio_x = 0, ratio_y = 0, ratio_w = 0.5, ratio_h = 1, + }, + overrides = { 'foo' }, + 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') + end) +end)