From bc7ea8602e9cf11a45aab2033502c772bac7e704 Mon Sep 17 00:00:00 2001 From: NiLuJe Date: Wed, 22 Nov 2023 18:56:47 +0100 Subject: [PATCH] UIManager: Don't block gestures for new widgets when input is disabled (#11122) They'll be disabled again when the widget in question is dismissed. This exposes a couple of semi-obvious but edge-casey footguns to the user, but a hardened implementation is way uglier. See PR for details. --- frontend/device/devicelistener.lua | 6 ++ frontend/ui/screensaver.lua | 3 + frontend/ui/uimanager.lua | 18 ++++++ .../ui/widget/container/inputcontainer.lua | 58 ++++++++++++------- 4 files changed, 64 insertions(+), 21 deletions(-) diff --git a/frontend/device/devicelistener.lua b/frontend/device/devicelistener.lua index b59455d49..d040a70f3 100644 --- a/frontend/device/devicelistener.lua +++ b/frontend/device/devicelistener.lua @@ -355,4 +355,10 @@ function DeviceListener:onFullRefresh() UIManager:setDirty(nil, "full") end +-- On resume, make sure we restore Gestures handling in InputContainer, to avoid confusion for scatter-brained users ;). +-- It's also helpful when the IgnoreTouchInput event is emitted by Dispatcher through other means than Gestures. +function DeviceListener:onResume() + UIManager:setIgnoreTouchInput(false) +end + return DeviceListener diff --git a/frontend/ui/screensaver.lua b/frontend/ui/screensaver.lua index 7ef2c0647..f05011130 100644 --- a/frontend/ui/screensaver.lua +++ b/frontend/ui/screensaver.lua @@ -753,6 +753,9 @@ function Screensaver:show() widget = addOverlayMessage(widget, message_height, self.overlay_message) end + -- NOTE: Make sure InputContainer gestures are not disabled, to prevent stupid interactions with UIManager on close. + UIManager:setIgnoreTouchInput(false) + if widget then self.screensaver_widget = ScreenSaverWidget:new{ widget = widget, diff --git a/frontend/ui/uimanager.lua b/frontend/ui/uimanager.lua index d48af20c7..3e2464946 100644 --- a/frontend/ui/uimanager.lua +++ b/frontend/ui/uimanager.lua @@ -42,6 +42,7 @@ local UIManager = { _gated_quit = nil, _prevent_standby_count = 0, _prev_prevent_standby_count = 0, + _input_gestures_disabled = false, event_hook = require("ui/hook_container"):new() } @@ -113,6 +114,12 @@ function UIManager:init() self:unsetRunForeverMode() end +-- Crappy wrapper because of circular dependencies +function UIManager:setIgnoreTouchInput(state) + local InputContainer = require("ui/widget/container/inputcontainer") + InputContainer:setIgnoreTouchInput(state) +end + --[[-- Registers and shows a widget. @@ -165,6 +172,13 @@ function UIManager:show(widget, refreshtype, refreshregion, x, y, refreshdither) Input.disable_double_tap = widget.disable_double_tap ~= false -- a widget may override tap interval (when it doesn't, nil restores the default) Input.tap_interval_override = widget.tap_interval_override + -- If input was disabled, re-enable it while this widget is shown so we can actually interact with it. + -- The only thing that could actually call show in this state is something automatic, so we need to be able to deal with it. + if UIManager._input_gestures_disabled then + logger.dbg("Gestures were disabled, temporarily re-enabling them to allow interaction with widget") + self:setIgnoreTouchInput(false) + widget._restored_input_gestures = true + end end --[[-- @@ -245,6 +259,10 @@ function UIManager:close(widget, refreshtype, refreshregion, refreshdither) end self:_refresh(refreshtype, refreshregion, refreshdither) end + if widget._restored_input_gestures then + logger.dbg("Widget is gone, disabling gesture handling again") + self:setIgnoreTouchInput(true) + end end --- Shift the execution times of all scheduled tasks. diff --git a/frontend/ui/widget/container/inputcontainer.lua b/frontend/ui/widget/container/inputcontainer.lua index 734eabf4f..a8a0779ed 100644 --- a/frontend/ui/widget/container/inputcontainer.lua +++ b/frontend/ui/widget/container/inputcontainer.lua @@ -41,6 +41,7 @@ local Geom = require("ui/geometry") local GestureRange = require("ui/gesturerange") local UIManager = require("ui/uimanager") local WidgetContainer = require("ui/widget/container/widgetcontainer") +local logger = require("logger") local Screen = Device.screen local _ = require("gettext") @@ -304,41 +305,56 @@ end -- [1] The most common implementation you'll see is a NOP for ReaderUI modules that defer gesture handling to ReaderUI. -- Notification also implements a simple one to dismiss notifications on any user input, -- which is something that doesn't impede our goal, which is why we don't need to deal with it. -function InputContainer:onIgnoreTouchInput(toggle) - local Notification = require("ui/widget/notification") - if toggle == false then +function InputContainer:setIgnoreTouchInput(state) + logger.dbg("InputContainer:setIgnoreTouchInput", state) + if state == true then + -- Replace the onGesture handler w/ the minimal one if that's not already the case + if not InputContainer._onGesture then + InputContainer._onGesture = InputContainer.onGesture + InputContainer.onGesture = InputContainer._onGestureFiltered + -- Notify UIManager so it knows what to do if a random popup shows up + UIManager._input_gestures_disabled = true + logger.dbg("Disabled InputContainer gesture handler") + + -- Notify our caller that the state changed + return true + end + elseif state == false then -- Restore the proper onGesture handler if we disabled it if InputContainer._onGesture then InputContainer.onGesture = InputContainer._onGesture InputContainer._onGesture = nil - Notification:notify("Restored touch input") + UIManager._input_gestures_disabled = false + logger.dbg("Restored InputContainer gesture handler") + + return true end - elseif toggle == true then - -- Replace the onGesture handler w/ the minimal one if that's not already the case - if not InputContainer._onGesture then - InputContainer._onGesture = InputContainer.onGesture - InputContainer.onGesture = InputContainer._onGestureFiltered - Notification:notify("Disabled touch input") + end + + -- We did not actually change the state + return false +end + +-- And the matching Event handler +function InputContainer:onIgnoreTouchInput(toggle) + local Notification = require("ui/widget/notification") + if toggle == true then + if self:setIgnoreTouchInput(true) then + Notification:notify(_("Disabled touch input")) + end + elseif toggle == false then + if self:setIgnoreTouchInput(false) then + Notification:notify(_("Restored touch input")) end else -- Toggle the current state - if InputContainer._onGesture then - return self:onIgnoreTouchInput(false) - else - return self:onIgnoreTouchInput(true) - end + return self:onIgnoreTouchInput(not UIManager._input_gestures_disabled) end -- We only affect the base class, once is enough ;). return true end -function InputContainer:onResume() - -- Always restore touch input on resume, to avoid confusion for scatter-brained users ;). - -- It's also helpful when the IgnoreTouchInput event is emitted by Dispatcher through other means than Gestures. - self:onIgnoreTouchInput(false) -end - function InputContainer:onInput(input, ignore_first_hold_release) local InputDialog = require("ui/widget/inputdialog") self.input_dialog = InputDialog:new{