@ -61,51 +61,29 @@ function UIManager:init()
self.poweroff_action = function ( )
self.poweroff_action = function ( )
self._entered_poweroff_stage = true
self._entered_poweroff_stage = true
Device.orig_rotation_mode = Device.screen : getRotationMode ( )
Device.orig_rotation_mode = Device.screen : getRotationMode ( )
self : broadcastEvent ( Event : new ( " Close " ) )
Screen : setRotationMode ( Screen.ORIENTATION_PORTRAIT )
Screen : setRotationMode ( Screen.ORIENTATION_PORTRAIT )
local Screensaver = require ( " ui/screensaver " )
local Screensaver = require ( " ui/screensaver " )
Screensaver : setup ( " poweroff " , _ ( " Powered off " ) )
Screensaver : setup ( " poweroff " , _ ( " Powered off " ) )
if Device : hasEinkScreen ( ) and Screensaver : modeIsImage ( ) then
if Screensaver : withBackground ( ) then
Screen : clear ( )
end
Screen : refreshFull ( )
end
Screensaver : show ( )
Screensaver : show ( )
if Device : needsScreenRefreshAfterResume ( ) then
self : nextTick ( function ( )
Screen : refreshFull ( )
end
UIManager : nextTick ( function ( )
Device : saveSettings ( )
Device : saveSettings ( )
if Device : isKobo ( ) then
self._exit_code = 88
end
self : broadcastEvent ( Event : new ( " Close " ) )
Device : powerOff ( )
Device : powerOff ( )
self : quit ( Device : isKobo ( ) and 88 )
end )
end )
end
end
self.reboot_action = function ( )
self.reboot_action = function ( )
self._entered_poweroff_stage = true
self._entered_poweroff_stage = true
Device.orig_rotation_mode = Device.screen : getRotationMode ( )
Device.orig_rotation_mode = Device.screen : getRotationMode ( )
self : broadcastEvent ( Event : new ( " Close " ) )
Screen : setRotationMode ( Screen.ORIENTATION_PORTRAIT )
Screen : setRotationMode ( Screen.ORIENTATION_PORTRAIT )
local Screensaver = require ( " ui/screensaver " )
local Screensaver = require ( " ui/screensaver " )
Screensaver : setup ( " reboot " , _ ( " Rebooting… " ) )
Screensaver : setup ( " reboot " , _ ( " Rebooting… " ) )
if Device : hasEinkScreen ( ) and Screensaver : modeIsImage ( ) then
if Screensaver : withBackground ( ) then
Screen : clear ( )
end
Screen : refreshFull ( )
end
Screensaver : show ( )
Screensaver : show ( )
if Device : needsScreenRefreshAfterResume ( ) then
self : nextTick ( function ( )
Screen : refreshFull ( )
end
UIManager : nextTick ( function ( )
Device : saveSettings ( )
Device : saveSettings ( )
if Device : isKobo ( ) then
self._exit_code = 88
end
self : broadcastEvent ( Event : new ( " Close " ) )
Device : reboot ( )
Device : reboot ( )
self : quit ( Device : isKobo ( ) and 88 )
end )
end )
end
end
@ -126,7 +104,7 @@ For more details about refreshtype, refreshregion & refreshdither see the descri
If refreshtype is omitted , no refresh will be enqueued at this time .
If refreshtype is omitted , no refresh will be enqueued at this time .
@ param widget a @ { ui.widget . widget | widget } object
@ param widget a @ { ui.widget . widget | widget } object
@ string refreshtype ` " full " ` , ` " flashpartial " ` , ` " flashui " ` , ` " partial" ` , ` " ui " ` , ` " fast " ` ( optional )
@ string refreshtype ` " full " ` , ` " flashpartial " ` , ` " flashui " ` , ` " [partial]" ` , ` " [ui] " ` , ` " partial" ` , ` " ui " ` , ` " fast " ` , ` " a2 " ` ( optional )
@ param refreshregion a rectangle @ { ui.geometry . Geom | Geom } object ( optional , requires refreshtype to be set )
@ param refreshregion a rectangle @ { ui.geometry . Geom | Geom } object ( optional , requires refreshtype to be set )
@ int x horizontal screen offset ( optional , ` 0 ` if omitted )
@ int x horizontal screen offset ( optional , ` 0 ` if omitted )
@ int y vertical screen offset ( optional , ` 0 ` if omitted )
@ int y vertical screen offset ( optional , ` 0 ` if omitted )
@ -135,7 +113,7 @@ If refreshtype is omitted, no refresh will be enqueued at this time.
] ]
] ]
function UIManager : show ( widget , refreshtype , refreshregion , x , y , refreshdither )
function UIManager : show ( widget , refreshtype , refreshregion , x , y , refreshdither )
if not widget then
if not widget then
logger.dbg ( " widget not exist to be shown " )
logger.dbg ( " attempted to show a nil widget " )
return
return
end
end
logger.dbg ( " show widget: " , widget.id or widget.name or tostring ( widget ) )
logger.dbg ( " show widget: " , widget.id or widget.name or tostring ( widget ) )
@ -181,14 +159,14 @@ For more details about refreshtype, refreshregion & refreshdither see the descri
If refreshtype is omitted , no extra refresh will be enqueued at this time , leaving only those from the uncovered widgets .
If refreshtype is omitted , no extra refresh will be enqueued at this time , leaving only those from the uncovered widgets .
@ param widget a @ { ui.widget . widget | widget } object
@ param widget a @ { ui.widget . widget | widget } object
@ string refreshtype ` " full " ` , ` " flashpartial " ` , ` " flashui " ` , ` " partial" ` , ` " ui " ` , ` " fast " ` ( optional )
@ string refreshtype ` " full " ` , ` " flashpartial " ` , ` " flashui " ` , ` " [partial]" ` , ` " [ui] " ` , ` " partial" ` , ` " ui " ` , ` " fast " ` , ` " a2 " ` ( optional )
@ param refreshregion a rectangle @ { ui.geometry . Geom | Geom } object ( optional , requires refreshtype to be set )
@ param refreshregion a rectangle @ { ui.geometry . Geom | Geom } object ( optional , requires refreshtype to be set )
@ bool refreshdither ` true ` if the refresh requires dithering ( optional , requires refreshtype to be set )
@ bool refreshdither ` true ` if the refresh requires dithering ( optional , requires refreshtype to be set )
@ see setDirty
@ see setDirty
] ]
] ]
function UIManager : close ( widget , refreshtype , refreshregion , refreshdither )
function UIManager : close ( widget , refreshtype , refreshregion , refreshdither )
if not widget then
if not widget then
logger.dbg ( " widget to be closed does not exis t" )
logger.dbg ( " attempted to close a nil widge t" )
return
return
end
end
logger.dbg ( " close widget: " , widget.name or widget.id or tostring ( widget ) )
logger.dbg ( " close widget: " , widget.name or widget.id or tostring ( widget ) )
@ -204,38 +182,39 @@ function UIManager:close(widget, refreshtype, refreshregion, refreshdither)
local start_idx = 1
local start_idx = 1
-- Then remove all references to that widget on stack and refresh.
-- Then remove all references to that widget on stack and refresh.
for i = # self._window_stack , 1 , - 1 do
for i = # self._window_stack , 1 , - 1 do
if self._window_stack [ i ] . widget == widget then
local w = self._window_stack [ i ] . widget
self._dirty [ self._window_stack [ i ] . widget ] = nil
if w == widget then
self._dirty [ w ] = nil
table.remove ( self._window_stack , i )
table.remove ( self._window_stack , i )
dirty = true
dirty = true
else
else
-- If anything else on the stack not already hidden by (i.e., below) a fullscreen widget was dithered, honor the hint
-- If anything else on the stack not already hidden by (i.e., below) a fullscreen widget was dithered, honor the hint
if self._ window_stack[ i ] . widget .dithered and not is_covered then
if w.dithered and not is_covered then
refreshdither = true
refreshdither = true
logger.dbg ( " Lower widget " , self._ window_stack[ i ] . widget .name or self._ window_stack[ i ] . widget .id or tostring ( self._ window_stack[ i ] . widget ) , " was dithered, honoring the dithering hint " )
logger.dbg ( " Lower widget " , w.name or w.id or tostring ( w) , " was dithered, honoring the dithering hint " )
end
end
-- Remember the uppermost widget that covers the full screen, so we don't bother calling setDirty on hidden (i.e., lower) widgets in the following dirty loop.
-- Remember the uppermost widget that covers the full screen, so we don't bother calling setDirty on hidden (i.e., lower) widgets in the following dirty loop.
-- _repaint already does that later on to skip the actual paintTo calls, so this ensures we limit the refresh queue to stuff that will actually get painted.
-- _repaint already does that later on to skip the actual paintTo calls, so this ensures we limit the refresh queue to stuff that will actually get painted.
if not is_covered and self._ window_stack[ i ] . widget .covers_fullscreen then
if not is_covered and w.covers_fullscreen then
is_covered = true
is_covered = true
start_idx = i
start_idx = i
logger.dbg ( " Lower widget " , self._ window_stack[ i ] . widget .name or self._ window_stack[ i ] . widget .id or tostring ( self._ window_stack[ i ] . widget ) , " covers the full screen " )
logger.dbg ( " Lower widget " , w.name or w.id or tostring ( w) , " covers the full screen " )
if i > 1 then
if i > 1 then
logger.dbg ( " not refreshing " , i - 1 , " covered widget(s) " )
logger.dbg ( " not refreshing " , i - 1 , " covered widget(s) " )
end
end
end
end
-- Set double tap to how the topmost specifying widget wants it
-- Set double tap to how the topmost specifying widget wants it
if requested_disable_double_tap == nil and self._ window_stack[ i ] . widget .disable_double_tap ~= nil then
if requested_disable_double_tap == nil and w.disable_double_tap ~= nil then
requested_disable_double_tap = self._ window_stack[ i ] . widget .disable_double_tap
requested_disable_double_tap = w.disable_double_tap
end
end
end
end
end
end
if requested_disable_double_tap ~= nil then
if requested_disable_double_tap ~= nil then
Input.disable_double_tap = requested_disable_double_tap
Input.disable_double_tap = requested_disable_double_tap
end
end
if # self._window_stack > 0 then
if self._window_stack [ 1 ] then
-- set tap interval override to what the topmost widget specifies (when it doesn't, nil restores the default)
-- set tap interval override to what the topmost widget specifies (when it doesn't, nil restores the default)
Input.tap_interval_override = self._window_stack [ # self._window_stack ] . widget.tap_interval_override
Input.tap_interval_override = self._window_stack [ # self._window_stack ] . widget.tap_interval_override
end
end
@ -281,7 +260,7 @@ function UIManager:schedule(sched_time, action, ...)
table.insert ( self._task_queue , p , {
table.insert ( self._task_queue , p , {
time = sched_time ,
time = sched_time ,
action = action ,
action = action ,
argc = select ( ' # ' , ... ) ,
argc = select ( " # " , ... ) ,
args = { ... } ,
args = { ... } ,
} )
} )
self._task_queue_dirty = true
self._task_queue_dirty = true
@ -336,16 +315,12 @@ necessary if the caller wants to unschedule action *before* it actually gets ins
@ see nextTick
@ see nextTick
] ]
] ]
function UIManager : tickAfterNext ( action , ... )
function UIManager : tickAfterNext ( action , ... )
-- Storing varargs is a bit iffy as we don't build LuaJIT w/ 5.2 compat, so we don't have access to table.pack...
-- c.f., http://lua-users.org/wiki/VarargTheSecondClassCitizen
local n = select ( ' # ' , ... )
local va = { ... }
-- We need to keep a reference to this anonymous function, as it is *NOT* quite `action` yet,
-- We need to keep a reference to this anonymous function, as it is *NOT* quite `action` yet,
-- and the caller might want to unschedule it early...
-- and the caller might want to unschedule it early...
local action_wrapper = function ( )
local action_wrapper = function ( ... )
self : nextTick ( action , unpack ( va , 1 , n ) )
self : nextTick ( action , ... )
end
end
self : nextTick ( action_wrapper )
self : nextTick ( action_wrapper , ... )
return action_wrapper
return action_wrapper
end
end
@ -416,14 +391,20 @@ Here's a quick rundown of what each refreshtype should be used for:
Can be promoted to flashing after ` FULL_REFRESH_COUNT ` refreshes .
Can be promoted to flashing after ` FULL_REFRESH_COUNT ` refreshes .
Don ' t abuse to avoid spurious flashes.
Don ' t abuse to avoid spurious flashes.
In practice , this means this should mostly always be limited to ReaderUI .
In practice , this means this should mostly always be limited to ReaderUI .
* ` [ partial ] ` : variant of partial that asks the driver not to merge this update with surrounding ones .
Equivalent to partial on platforms where this distinction is not implemented .
* ` ui ` : medium fidelity refresh ( e.g . , mixed content ) .
* ` ui ` : medium fidelity refresh ( e.g . , mixed content ) .
Should apply to most UI elements .
Should apply to most UI elements .
When in doubt , use this .
When in doubt , use this .
* ` fast ` : low fidelity refresh ( e.g . , monochrome content ) .
* ` [ ui ] ` : variant of ui that asks the driver not to merge this update with surrounding ones .
Equivalent to ui on platforms where this distinction is not implemented .
* ` fast ` : low fidelity refresh ( e.g . , monochrome content ( technically , from any to B & W ) ) .
Should apply to most highlighting effects achieved through inversion .
Should apply to most highlighting effects achieved through inversion .
Note that if your highlighted element contains text ,
Note that if your highlighted element contains text ,
you might want to keep the unhighlight refresh as ` " ui " ` instead , for crisper text .
you might want to keep the unhighlight refresh as ` " ui " ` instead , for crisper text .
( Or optimize that refresh away entirely , if you can get away with it ) .
( Or optimize that refresh away entirely , if you can get away with it ) .
* ` a2 ` : low fidelity refresh ( e.g . , monochrome content ( technically , from B & W to B & W only ) ) .
Should be limited to very specific use - cases ( e.g . , keyboard )
* ` flashui ` : like ` ui ` , but flashing .
* ` flashui ` : like ` ui ` , but flashing .
Can be used when showing a UI element for the first time , or when closing one , to avoid ghosting .
Can be used when showing a UI element for the first time , or when closing one , to avoid ghosting .
* ` flashpartial ` : like ` partial ` , but flashing ( and not counting towards flashing promotions ) .
* ` flashpartial ` : like ` partial ` , but flashing ( and not counting towards flashing promotions ) .
@ -501,7 +482,7 @@ UIManager:setDirty(self.widget, "partial", Geom:new{x=10,y=10,w=100,h=50})
UIManager : setDirty ( self.widget , function ( ) return " ui " , self.someelement . dimen end )
UIManager : setDirty ( self.widget , function ( ) return " ui " , self.someelement . dimen end )
@ param widget a window - level widget object , ` " all " ` , or ` nil `
@ param widget a window - level widget object , ` " all " ` , or ` nil `
@ param refreshtype ` " full " ` , ` " flashpartial " ` , ` " flashui " ` , ` " partial" ` , ` " ui " ` , ` " fast " ` ( or a lambda , see description above )
@ param refreshtype ` " full " ` , ` " flashpartial " ` , ` " flashui " ` , ` " [partial]" ` , ` " [ui] " ` , ` " partial" ` , ` " ui " ` , ` " fast " ` , ` " a2 " ` ( or a lambda , see description above )
@ param refreshregion a rectangle @ { ui.geometry . Geom | Geom } object ( optional , omitting it means the region will cover the full screen )
@ param refreshregion a rectangle @ { ui.geometry . Geom | Geom } object ( optional , omitting it means the region will cover the full screen )
@ bool refreshdither ` true ` if widget requires dithering ( optional )
@ bool refreshdither ` true ` if widget requires dithering ( optional )
] ]
] ]
@ -509,10 +490,11 @@ function UIManager:setDirty(widget, refreshtype, refreshregion, refreshdither)
if widget then
if widget then
if widget == " all " then
if widget == " all " then
-- special case: set all top-level widgets as being "dirty".
-- special case: set all top-level widgets as being "dirty".
for i = 1 , # self._window_stack do
for _ , window in ipairs ( self._window_stack ) do
self._dirty [ self._window_stack [ i ] . widget ] = true
local w = window.widget
self._dirty [ w ] = true
-- If any of 'em were dithered, honor their dithering hint
-- If any of 'em were dithered, honor their dithering hint
if self._ window_stack[ i ] . widget .dithered then
if w.dithered then
-- NOTE: That works when refreshtype is NOT a function,
-- NOTE: That works when refreshtype is NOT a function,
-- which is why _repaint does another pass of this check ;).
-- which is why _repaint does another pass of this check ;).
logger.dbg ( " setDirty on all widgets: found a dithered widget, infecting the refresh queue " )
logger.dbg ( " setDirty on all widgets: found a dithered widget, infecting the refresh queue " )
@ -528,16 +510,17 @@ function UIManager:setDirty(widget, refreshtype, refreshregion, refreshdither)
-- NOTE: We only ever check the dirty flag on top-level widgets, so only set it there!
-- NOTE: We only ever check the dirty flag on top-level widgets, so only set it there!
-- Enable verbose debug to catch misbehaving widgets via our post-guard.
-- Enable verbose debug to catch misbehaving widgets via our post-guard.
for i = # self._window_stack , 1 , - 1 do
for i = # self._window_stack , 1 , - 1 do
local w = self._window_stack [ i ] . widget
if handle_alpha then
if handle_alpha then
self._dirty [ self._ window_stack[ i ] . widget ] = true
self._dirty [ w] = true
logger.dbg ( " setDirty: Marking as dirty widget: " , self._ window_stack[ i ] . widget .name or self._ window_stack[ i ] . widget .id or tostring ( self._ window_stack[ i ] . widget ) , " because it's below translucent widget: " , widget.name or widget.id or tostring ( widget ) )
logger.dbg ( " setDirty: Marking as dirty widget: " , w.name or w.id or tostring ( w) , " because it's below translucent widget: " , widget.name or widget.id or tostring ( widget ) )
-- Stop flagging widgets at the uppermost one that covers the full screen
-- Stop flagging widgets at the uppermost one that covers the full screen
if self._ window_stack[ i ] . widget .covers_fullscreen then
if w.covers_fullscreen then
break
break
end
end
end
end
if self._ window_stack[ i ] . widget == widget then
if w == widget then
self._dirty [ widget ] = true
self._dirty [ widget ] = true
-- We've got a match, now check if it's translucent...
-- We've got a match, now check if it's translucent...
@ -592,6 +575,11 @@ function UIManager:setDirty(widget, refreshtype, refreshregion, refreshdither)
end
end
end
end
end
end
--[[
-- NOTE: While nice in theory, this is *extremely* verbose in practice,
-- because most widgets will call setDirty at least once during their initialization,
-- and that happens before they make it to the window stack...
-- Plus, setDirty(nil, ...) is a completely valid use-case with documented semantics...
dbg : guard ( UIManager , ' setDirty ' ,
dbg : guard ( UIManager , ' setDirty ' ,
nil ,
nil ,
function ( self , widget , refreshtype , refreshregion , refreshdither )
function ( self , widget , refreshtype , refreshregion , refreshdither )
@ -609,6 +597,7 @@ dbg:guard(UIManager, 'setDirty',
dbg : v ( " INFO: invalid widget for setDirty() " , debug.traceback ( ) )
dbg : v ( " INFO: invalid widget for setDirty() " , debug.traceback ( ) )
end
end
end )
end )
--]]
--[[--
--[[--
Clear the full repaint & refresh queues .
Clear the full repaint & refresh queues .
@ -684,14 +673,16 @@ end
--- Get top widget (name if possible, ref otherwise).
--- Get top widget (name if possible, ref otherwise).
function UIManager : getTopWidget ( )
function UIManager : getTopWidget ( )
local top = self._window_stack [ # self._window_stack ]
if not self._window_stack [ 1 ] then
if not top or not top.widget then
-- No widgets in the stack, bye!
return nil
return nil
end
end
if top.widget . name then
return top.widget . name
local widget = self._window_stack [ # self._window_stack ] . widget
if widget.name then
return widget.name
end
end
return top.widget
return widget
end
end
--[[--
--[[--
@ -710,19 +701,16 @@ function UIManager:getSecondTopmostWidget()
-- Because everything is terrible, you can actually instantiate multiple VirtualKeyboards,
-- Because everything is terrible, you can actually instantiate multiple VirtualKeyboards,
-- and they'll stack at the top, so, loop until we get something that *isn't* VK...
-- and they'll stack at the top, so, loop until we get something that *isn't* VK...
for i = # self._window_stack - 1 , 1 , - 1 do
for i = # self._window_stack - 1 , 1 , - 1 do
local sec = self._window_stack [ i ]
local widget = self._window_stack [ i ] . widget
if not sec or not sec.widget then
return nil
end
if sec. widget . name then
if widget.name then
if sec. widget . name ~= " VirtualKeyboard " then
if widget.name ~= " VirtualKeyboard " then
return sec. widget . name
return widget.name
end
end
-- Meaning if name is set, and is set to VK => continue, as we want the *next* widget.
-- Meaning if name is set, and is set to VK => continue, as we want the *next* widget.
-- I *really* miss the continue keyword, Lua :/.
-- I *really* miss the continue keyword, Lua :/.
else
else
return sec. widget
return widget
end
end
end
end
@ -732,9 +720,10 @@ end
--- Check if a widget is still in the window stack, or is a subwidget of a widget still in the window stack.
--- Check if a widget is still in the window stack, or is a subwidget of a widget still in the window stack.
function UIManager : isSubwidgetShown ( widget , max_depth )
function UIManager : isSubwidgetShown ( widget , max_depth )
for i = # self._window_stack , 1 , - 1 do
for i = # self._window_stack , 1 , - 1 do
local matched , depth = util.arrayReferences ( self._window_stack [ i ] . widget , widget , max_depth )
local w = self._window_stack [ i ] . widget
local matched , depth = util.arrayReferences ( w , widget , max_depth )
if matched then
if matched then
return matched , depth , self._ window_stack[ i ] . widget
return matched , depth , w
end
end
end
end
return false
return false
@ -750,19 +739,11 @@ function UIManager:isWidgetShown(widget)
return false
return false
end
end
--[[--
Returns the region of the previous refresh .
@ return a rectangle @ { ui.geometry . Geom | Geom } object
] ]
function UIManager : getPreviousRefreshRegion ( )
return self._last_refresh_region
end
--- Signals to quit.
--- Signals to quit.
function UIManager : quit ( )
function UIManager : quit ( exit_code )
if not self._running then return end
if not self._running then return end
logger.info ( " quitting uimanager " )
logger.info ( " quitting uimanager with exit code: " , exit_code or 0 )
self._exit_code = exit_code
self._task_queue_dirty = false
self._task_queue_dirty = false
self._running = false
self._running = false
self._run_forever = nil
self._run_forever = nil
@ -791,25 +772,29 @@ which itself will take care of propagating an event to its members.
@ param event an @ { ui.event . Event | Event } object
@ param event an @ { ui.event . Event | Event } object
] ]
] ]
function UIManager : sendEvent ( event )
function UIManager : sendEvent ( event )
if # self._window_stack == 0 then return end
if not self._window_stack [ 1 ] then
-- No widgets in the stack!
return
end
-- The top widget gets to be the first to get the event
-- The top widget gets to be the first to get the event
local top_widget = self._window_stack [ # self._window_stack ]
local top_widget = self._window_stack [ # self._window_stack ] . widget
-- A toast widget gets closed by any event, and
-- A toast widget gets closed by any event, and lets the event be handled by a lower widget.
-- lets the event be handled by a lower widget
-- (Notification is our only widget flagged as such).
-- (Notification is our single widget with toast=true)
while top_widget.toast do -- close them all
while top_widget.widget . toast do -- close them all
self : close ( top_widget )
self : close ( top_widget.widget )
if not self._window_stack [ 1 ] then
if # self._window_stack == 0 then return end
return
top_widget = self._window_stack [ # self._window_stack ]
end
top_widget = self._window_stack [ # self._window_stack ] . widget
end
end
if top_widget .widget : handleEvent ( event ) then
if top_widget : handleEvent ( event ) then
return
return
end
end
if top_widget. widget. active_widgets then
if top_widget. active_widgets then
for _ , active_widget in ipairs ( top_widget. widget. active_widgets) do
for _ , active_widget in ipairs ( top_widget. active_widgets) do
if active_widget : handleEvent ( event ) then return end
if active_widget : handleEvent ( event ) then return end
end
end
end
end
@ -824,20 +809,24 @@ function UIManager:sendEvent(event)
local checked_widgets = { top_widget }
local checked_widgets = { top_widget }
local i = # self._window_stack
local i = # self._window_stack
while i > 0 do
while i > 0 do
local widget = self._window_stack [ i ]
local widget = self._window_stack [ i ] . widget
if checked_widgets [ widget ] == nil then
if not checked_widgets [ widget ] then
checked_widgets [ widget ] = true
checked_widgets [ widget ] = true
-- Widget's active widgets have precedence to handle this event
-- Widget's active widgets have precedence to handle this event
-- NOTE: While FileManager only has a single (screenshotter), ReaderUI has a few active_widgets.
-- NOTE: ReaderUI & FileManager have their registered modules referenced as such.
if widget.widget . active_widgets then
if widget.active_widgets then
for _ , active_widget in ipairs ( widget.widget . active_widgets ) do
for _ , active_widget in ipairs ( widget.active_widgets ) do
if active_widget : handleEvent ( event ) then return end
if active_widget : handleEvent ( event ) then
return
end
end
end
end
end
if widget. widget. is_always_active then
if widget. is_always_active then
-- Widget itself is flagged always active, let it handle the event
-- Widget itself is flagged always active, let it handle the event
-- NOTE: is_always_active widgets currently are widgets that want to show a VirtualKeyboard or listen to Dispatcher events
-- NOTE: is_always_active widgets are currently widgets that want to show a VirtualKeyboard or listen to Dispatcher events
if widget.widget : handleEvent ( event ) then return end
if widget : handleEvent ( event ) then
return
end
end
end
i = # self._window_stack
i = # self._window_stack
else
else
@ -857,10 +846,10 @@ function UIManager:broadcastEvent(event)
local checked_widgets = { }
local checked_widgets = { }
local i = # self._window_stack
local i = # self._window_stack
while i > 0 do
while i > 0 do
local widget = self._window_stack [ i ]
local widget = self._window_stack [ i ] . widget
if checked_widgets [ widget ] == nil then
if not checked_widgets [ widget ] then
checked_widgets [ widget ] = true
checked_widgets [ widget ] = true
widget .widget : handleEvent ( event )
widget : handleEvent ( event )
i = # self._window_stack
i = # self._window_stack
else
else
i = i - 1
i = i - 1
@ -944,17 +933,20 @@ function UIManager:getElapsedTimeSinceBoot()
end
end
-- precedence of refresh modes:
-- precedence of refresh modes:
local refresh_modes = { fast = 1 , ui = 2 , partial = 3 , flashui = 4 , flashpartial = 5 , full = 6 }
local refresh_modes = { a2 = 1 , fast = 2 , ui = 3 , partial = 4 , [ " [ui] " ] = 5 , [ " [partial] " ] = 6 , flashui = 7 , flashpartial = 8 , full = 9 }
-- NOTE: We might want to introduce a "force_ fast " that points to fast, but has the highest priority,
-- NOTE: We might want to introduce a "force_ a2 " that points to fast, but has the highest priority,
-- for the few cases where we might *really* want to enforce fast (for stuff like panning or skimming?).
-- for the few cases where we might *really* want to enforce fast (for stuff like panning or skimming?).
-- refresh methods in framebuffer implementation
-- refresh methods in framebuffer implementation
local refresh_methods = {
local refresh_methods = {
fast = " refreshFast " ,
a2 = Screen.refreshA2 ,
ui = " refreshUI " ,
fast = Screen.refreshFast ,
partial = " refreshPartial " ,
ui = Screen.refreshUI ,
flashui = " refreshFlashUI " ,
partial = Screen.refreshPartial ,
flashpartial = " refreshFlashPartial " ,
[ " [ui] " ] = Screen.refreshNoMergeUI ,
full = " refreshFull " ,
[ " [partial] " ] = Screen.refreshNoMergePartial ,
flashui = Screen.refreshFlashUI ,
flashpartial = Screen.refreshFlashPartial ,
full = Screen.refreshFull ,
}
}
--[[
--[[
@ -992,7 +984,7 @@ Widgets call this in their `paintTo()` method in order to notify
UIManager that a certain part of the screen is to be refreshed .
UIManager that a certain part of the screen is to be refreshed .
@ string mode
@ string mode
refresh mode ( ` " full " ` , ` " flashpartial " ` , ` " flashui " ` , ` " partial" ` , ` " ui " ` , ` " fast " ` )
refresh mode ( ` " full " ` , ` " flashpartial " ` , ` " flashui " ` , ` " [partial]" ` , ` " [ui] " ` , ` " partial" ` , ` " ui " ` , ` " fast " ` , ` " a2 " ` )
@ param region
@ param region
A rectangle @ { ui.geometry . Geom | Geom } object that specifies the region to be updated .
A rectangle @ { ui.geometry . Geom | Geom } object that specifies the region to be updated .
Optional , update will affect whole screen if not specified .
Optional , update will affect whole screen if not specified .
@ -1071,16 +1063,16 @@ function UIManager:_refresh(mode, region, dither)
-- (e.g., multiple setDirty calls queued when showing/closing a widget because of update mechanisms),
-- (e.g., multiple setDirty calls queued when showing/closing a widget because of update mechanisms),
-- as well as a few actually effective merges
-- as well as a few actually effective merges
-- (e.g., the disappearance of a selection HL with the following menu update).
-- (e.g., the disappearance of a selection HL with the following menu update).
for i = 1 , # self._refresh_stack do
for i , refresh in ipairs ( self._refresh_stack ) do
-- Check for collision with refreshes that are already enqueued
-- Check for collision with refreshes that are already enqueued
-- NOTE: intersect *means* intersect: we won't merge edge-to-edge regions (but the EPDC probably will).
-- NOTE: intersect *means* intersect: we won't merge edge-to-edge regions (but the EPDC probably will).
if region : intersectWith ( self._ refresh_stack[ i ] . region) then
if region : intersectWith ( refresh.region) then
-- combine both refreshes' regions
-- combine both refreshes' regions
local combined = region : combine ( self._ refresh_stack[ i ] . region)
local combined = region : combine ( refresh.region)
-- update the mode, if needed
-- update the mode, if needed
mode = update_mode ( mode , self._ refresh_stack[ i ] . mode)
mode = update_mode ( mode , refresh.mode)
-- dithering hints are viral, one is enough to infect the whole queue
-- dithering hints are viral, one is enough to infect the whole queue
dither = update_dither ( dither , self._ refresh_stack[ i ] . dither)
dither = update_dither ( dither , refresh.dither)
-- remove colliding refresh
-- remove colliding refresh
table.remove ( self._refresh_stack , i )
table.remove ( self._refresh_stack , i )
-- and try again with combined data
-- and try again with combined data
@ -1134,26 +1126,27 @@ function UIManager:_repaint()
--]]
--]]
for i = start_idx , # self._window_stack do
for i = start_idx , # self._window_stack do
local widget = self._window_stack [ i ]
local window = self._window_stack [ i ]
local widget = window.widget
-- paint if current widget or any widget underneath is dirty
-- paint if current widget or any widget underneath is dirty
if dirty or self._dirty [ widget .widget ] then
if dirty or self._dirty [ widget ] then
-- pass hint to widget that we got when setting widget dirty
-- pass hint to widget that we got when setting widget dirty
-- the widget can use this to decide which parts should be refreshed
-- the widget can use this to decide which parts should be refreshed
logger.dbg ( " painting widget: " , widget. widget. name or widget. widget. id or tostring ( widget ) )
logger.dbg ( " painting widget: " , widget. name or widget. id or tostring ( widget ) )
Screen : beforePaint ( )
Screen : beforePaint ( )
-- NOTE: Nothing actually seems to use the final argument?
-- NOTE: Nothing actually seems to use the final argument?
-- Could be used by widgets to know whether they're being repainted because they're actually dirty (it's true),
-- Could be used by widgets to know whether they're being repainted because they're actually dirty (it's true),
-- or because something below them was (it's nil).
-- or because something below them was (it's nil).
widget .widget : paintTo ( Screen.bb , wi dget.x, widget.y , self._dirty [ widget. widget] )
widget : paintTo ( Screen.bb , wi ndow.x, window.y , self._dirty [ widget] )
-- and remove from list after painting
-- and remove from list after painting
self._dirty [ widget .widget ] = nil
self._dirty [ widget ] = nil
-- trigger a repaint for every widget above us, too
-- trigger a repaint for every widget above us, too
dirty = true
dirty = true
-- if any of 'em were dithered, we'll want to dither the final refresh
-- if any of 'em were dithered, we'll want to dither the final refresh
if widget. widget. dithered then
if widget. dithered then
logger.dbg ( " _repaint: it was dithered, infecting the refresh queue " )
logger.dbg ( " _repaint: it was dithered, infecting the refresh queue " )
dithered = true
dithered = true
end
end
@ -1165,13 +1158,15 @@ function UIManager:_repaint()
local refreshtype , region , dither = refreshfunc ( )
local refreshtype , region , dither = refreshfunc ( )
-- honor dithering hints from *anywhere* in the dirty stack
-- honor dithering hints from *anywhere* in the dirty stack
dither = update_dither ( dither , dithered )
dither = update_dither ( dither , dithered )
if refreshtype then self : _refresh ( refreshtype , region , dither ) end
if refreshtype then
self : _refresh ( refreshtype , region , dither )
end
end
end
self._refresh_func_stack = { }
self._refresh_func_stack = { }
-- we should have at least one refresh if we did repaint. If we don't, we
-- we should have at least one refresh if we did repaint. If we don't, we
-- add one now and log a warning if we are debugging
-- add one now and log a warning if we are debugging
if dirty and # self._refresh_stack == 0 then
if dirty and not self._refresh_stack [ 1 ] then
logger.dbg ( " no refresh got enqueued. Will do a partial full screen refresh, which might be inefficient " )
logger.dbg ( " no refresh got enqueued. Will do a partial full screen refresh, which might be inefficient " )
self : _refresh ( " partial " )
self : _refresh ( " partial " )
end
end
@ -1185,9 +1180,12 @@ function UIManager:_repaint()
refresh.dither = nil
refresh.dither = nil
end
end
dbg : v ( " triggering refresh " , refresh )
dbg : v ( " triggering refresh " , refresh )
--[[
-- Remember the refresh region
-- Remember the refresh region
self._last_refresh_region = refresh.region
self._last_refresh_region = refresh.region : copy ( )
Screen [ refresh_methods [ refresh.mode ] ] ( Screen ,
--]]
refresh_methods [ refresh.mode ] ( Screen ,
refresh.region . x , refresh.region . y ,
refresh.region . x , refresh.region . y ,
refresh.region . w , refresh.region . h ,
refresh.region . w , refresh.region . h ,
refresh.dither )
refresh.dither )
@ -1313,7 +1311,7 @@ function UIManager:handleInputEvent(input_event)
if handler then
if handler then
handler ( input_event )
handler ( input_event )
else
else
self.event_handlers [" __default__ " ] ( input_event )
self.event_handlers .__default__ ( input_event )
end
end
end
end
@ -1345,7 +1343,7 @@ function UIManager:handleInput()
--dbg("---------------------------------------------------")
--dbg("---------------------------------------------------")
-- stop when we have no window to show
-- stop when we have no window to show
if # self._window_stack == 0 and not self._run_forever then
if not self._window_stack [ 1 ] and not self._run_forever then
logger.info ( " no dialog left to show " )
logger.info ( " no dialog left to show " )
self : quit ( )
self : quit ( )
return nil
return nil
@ -1365,7 +1363,7 @@ function UIManager:handleInput()
local wait_us = self.INPUT_TIMEOUT
local wait_us = self.INPUT_TIMEOUT
-- If we have any ZMQs registered, ZMQ_TIMEOUT is another upper bound.
-- If we have any ZMQs registered, ZMQ_TIMEOUT is another upper bound.
if # self._zeromqs > 0 then
if self._zeromqs [ 1 ] then
wait_us = math.min ( wait_us or math.huge , self.ZMQ_TIMEOUT )
wait_us = math.min ( wait_us or math.huge , self.ZMQ_TIMEOUT )
end
end
@ -1419,7 +1417,6 @@ function UIManager:handleInput()
xpcall ( function ( ) self : handleInput ( ) end , function ( err )
xpcall ( function ( ) self : handleInput ( ) end , function ( err )
io.stderr : write ( err .. " \n " )
io.stderr : write ( err .. " \n " )
io.stderr : write ( debug.traceback ( ) .. " \n " )
io.stderr : write ( debug.traceback ( ) .. " \n " )
io.stderr : flush ( )
self.looper : close ( )
self.looper : close ( )
os.exit ( 1 , true )
os.exit ( 1 , true )
end )
end )
@ -1480,28 +1477,28 @@ This function usually puts the device into suspension.
] ]
] ]
function UIManager : suspend ( )
function UIManager : suspend ( )
-- Should always exist, as defined in `generic/device` or overwritten with `setEventHandlers`
-- Should always exist, as defined in `generic/device` or overwritten with `setEventHandlers`
if self.event_handlers [" Suspend " ] then
if self.event_handlers .Suspend then
-- Give the other event handlers a chance to be executed.
-- Give the other event handlers a chance to be executed.
-- `Suspend` and `Resume` events will be sent by the handler
-- `Suspend` and `Resume` events will be sent by the handler
UIManager : nextTick ( self.event_handlers [" Suspend " ] )
UIManager : nextTick ( self.event_handlers .Suspend )
end
end
end
end
function UIManager : reboot ( )
function UIManager : reboot ( )
-- Should always exist, as defined in `generic/device` or overwritten with `setEventHandlers`
-- Should always exist, as defined in `generic/device` or overwritten with `setEventHandlers`
if self.event_handlers [" Reboot " ] then
if self.event_handlers .Reboot then
-- Give the other event handlers a chance to be executed.
-- Give the other event handlers a chance to be executed.
-- 'Reboot' event will be sent by the handler
-- 'Reboot' event will be sent by the handler
UIManager : nextTick ( self.event_handlers [" Reboot " ] )
UIManager : nextTick ( self.event_handlers .Reboot )
end
end
end
end
function UIManager : powerOff ( )
function UIManager : powerOff ( )
-- Should always exist, as defined in `generic/device` or overwritten with `setEventHandlers`
-- Should always exist, as defined in `generic/device` or overwritten with `setEventHandlers`
if self.event_handlers [" PowerOff " ] then
if self.event_handlers .PowerOff then
-- Give the other event handlers a chance to be executed.
-- Give the other event handlers a chance to be executed.
-- 'PowerOff' event will be sent by the handler
-- 'PowerOff' event will be sent by the handler
UIManager : nextTick ( self.event_handlers [" PowerOff " ] )
UIManager : nextTick ( self.event_handlers .PowerOff )
end
end
end
end
@ -1557,15 +1554,13 @@ end
--- Sanely restart KOReader (on supported platforms).
--- Sanely restart KOReader (on supported platforms).
function UIManager : restartKOReader ( )
function UIManager : restartKOReader ( )
self : quit ( )
-- This is just a magic number to indicate the restart request for shell scripts.
-- This is just a magic number to indicate the restart request for shell scripts.
self ._exit_code = 85
self : quit ( 85 )
end
end
--- Sanely abort KOReader (e.g., exit sanely, but with a non-zero return code).
--- Sanely abort KOReader (e.g., exit sanely, but with a non-zero return code).
function UIManager : abort ( )
function UIManager : abort ( )
self : quit ( )
self : quit ( 1 )
self._exit_code = 1
end
end
UIManager : init ( )
UIManager : init ( )