diff --git a/frontend/ui/uimanager.lua b/frontend/ui/uimanager.lua index 0509f54ac..9ae907ac3 100644 --- a/frontend/ui/uimanager.lua +++ b/frontend/ui/uimanager.lua @@ -381,19 +381,33 @@ end -- transmit an event to an active widget function UIManager:sendEvent(event) if #self._window_stack == 0 then return end + + local top_widget = self._window_stack[#self._window_stack] -- top level widget has first access to the event - if self._window_stack[#self._window_stack].widget:handleEvent(event) then + if top_widget.widget:handleEvent(event) then return end - - -- if the event is not consumed, active widgets can access it - for _, widget in ipairs(self._window_stack) do - if widget.widget.is_always_active then - if widget.widget:handleEvent(event) then return end + if top_widget.widget.active_widgets then + for _, active_widget in ipairs(top_widget.widget.active_widgets) do + if active_widget:handleEvent(event) then return end end - if widget.widget.active_widgets then - for _, active_widget in ipairs(widget.widget.active_widgets) do - if active_widget:handleEvent(event) then return end + end + + -- if the event is not consumed, active widgets (from top to bottom) can + -- access it. NOTE: _window_stack can shrink on close event + local checked_widgets = {top_widget} + for i = #self._window_stack-1, 1, -1 do + local widget = self._window_stack[i] + if checked_widgets[widget] == nil then + if widget.widget.is_always_active then + checked_widgets[widget] = true + if widget.widget:handleEvent(event) then return end + end + if widget.widget.active_widgets then + checked_widgets[widget] = true + for _, active_widget in ipairs(widget.widget.active_widgets) do + if active_widget:handleEvent(event) then return end + end end end end diff --git a/spec/unit/uimanager_spec.lua b/spec/unit/uimanager_spec.lua index e74ed26ba..1b2a530dd 100644 --- a/spec/unit/uimanager_spec.lua +++ b/spec/unit/uimanager_spec.lua @@ -191,4 +191,83 @@ describe("UIManager spec", function() UIManager.auto_suspend_action) Device.isKobo = old_is_kobo end) + + it("should check active widgets in order", function() + local call_signals = {false, false, false} + UIManager._window_stack = { + { + widget = { + is_always_active = true, + handleEvent = function() + call_signals[1] = true + return true + end + } + }, + { + widget = { + is_always_active = true, + handleEvent = function() + call_signals[2] = true + return true + end + } + }, + { + widget = { + is_always_active = true, + handleEvent = function() + call_signals[3] = true + return true + end + } + }, + {widget = {handleEvent = function()end}}, + } + + UIManager:sendEvent("foo") + assert.falsy(call_signals[1]) + assert.falsy(call_signals[2]) + assert.truthy(call_signals[3]) + end) + + it("should handle stack change when checking for active widgets", function() + -- this senario should only happen when other active widgets + -- are closed by the one widget's handleEvent + + local call_signals = {0, 0, 0} + UIManager._window_stack = { + { + widget = { + is_always_active = true, + handleEvent = function() + call_signals[1] = call_signals[1] + 1 + end + } + }, + { + widget = { + is_always_active = true, + handleEvent = function() + call_signals[2] = call_signals[2] + 1 + end + } + }, + { + widget = { + is_always_active = true, + handleEvent = function() + call_signals[3] = call_signals[3] + 1 + table.remove(UIManager._window_stack, 2) + end + } + }, + {widget = {handleEvent = function()end}}, + } + + UIManager:sendEvent("foo") + assert.is.same(call_signals[1], 1) + assert.is.same(call_signals[2], 0) + assert.is.same(call_signals[3], 1) + end) end)