2014-10-30 18:42:18 +00:00
local Device = require ( " device " )
local Screen = Device.screen
local Input = require ( " device " ) . input
2013-10-18 20:38:07 +00:00
local Event = require ( " ui/event " )
2014-08-26 10:10:26 +00:00
local util = require ( " ffi/util " )
2013-10-22 18:51:29 +00:00
local DEBUG = require ( " dbg " )
2013-10-24 13:45:02 +00:00
local _ = require ( " gettext " )
2012-04-22 19:29:48 +00:00
2014-07-07 23:04:41 +00:00
-- NOTE: Those have been confirmed on Kindle devices. Might be completely different on Kobo (except for AUTO)!
2014-06-23 14:33:11 +00:00
local WAVEFORM_MODE_INIT = 0x0 -- Screen goes to white (clears)
local WAVEFORM_MODE_DU = 0x1 -- Grey->white/grey->black
local WAVEFORM_MODE_GC16 = 0x2 -- High fidelity (flashing)
local WAVEFORM_MODE_GC4 = WAVEFORM_MODE_GC16 -- For compatibility
local WAVEFORM_MODE_GC16_FAST = 0x3 -- Medium fidelity
local WAVEFORM_MODE_A2 = 0x4 -- Faster but even lower fidelity
local WAVEFORM_MODE_GL16 = 0x5 -- High fidelity from white transition
local WAVEFORM_MODE_GL16_FAST = 0x6 -- Medium fidelity from white transition
2014-07-06 16:43:50 +00:00
-- Kindle FW >= 5.3
2014-07-06 16:02:58 +00:00
local WAVEFORM_MODE_DU4 = 0x7 -- Medium fidelity 4 level of gray direct update
2014-07-06 16:43:50 +00:00
-- Kindle PW2
2014-07-06 16:02:58 +00:00
local WAVEFORM_MODE_REAGL = 0x8 -- Ghost compensation waveform
local WAVEFORM_MODE_REAGLD = 0x9 -- Ghost compensation waveform with dithering
2014-07-06 16:43:50 +00:00
2014-06-23 14:33:11 +00:00
local WAVEFORM_MODE_AUTO = 0x101
2012-04-22 19:29:48 +00:00
-- there is only one instance of this
2013-10-18 20:38:07 +00:00
local UIManager = {
2014-03-13 13:52:43 +00:00
default_refresh_type = 0 , -- 0 for partial refresh, 1 for full refresh
default_waveform_mode = WAVEFORM_MODE_GC16 , -- high fidelity waveform
fast_waveform_mode = WAVEFORM_MODE_A2 ,
2014-07-07 23:21:05 +00:00
full_refresh_waveform_mode = WAVEFORM_MODE_GC16 ,
partial_refresh_waveform_mode = WAVEFORM_MODE_GC16 ,
2014-03-13 13:52:43 +00:00
-- force to repaint all the widget is stack, will be reset to false
-- after each ui loop
repaint_all = false ,
-- force to do full refresh, will be reset to false
-- after each ui loop
full_refresh = false ,
2014-07-06 19:38:13 +00:00
-- force to do partial refresh, will be reset to false
2014-03-13 13:52:43 +00:00
-- after each ui loop
2014-07-06 19:38:13 +00:00
partial_refresh = false ,
2014-03-13 13:52:43 +00:00
-- trigger a full refresh when counter reaches FULL_REFRESH_COUNT
2014-07-26 14:22:52 +00:00
FULL_REFRESH_COUNT = G_reader_settings : readSetting ( " full_refresh_count " ) or DRCOUNTMAX ,
2014-03-13 13:52:43 +00:00
refresh_count = 0 ,
2013-03-12 17:18:53 +00:00
2014-06-26 17:37:33 +00:00
event_handlers = nil ,
2014-03-13 13:52:43 +00:00
_running = true ,
_window_stack = { } ,
_execution_stack = { } ,
2014-06-23 14:33:11 +00:00
_dirty = { } ,
_zeromqs = { } ,
2012-04-22 19:29:48 +00:00
}
2014-06-26 17:37:33 +00:00
function UIManager : init ( )
self.event_handlers = {
__default__ = function ( input_event )
self : sendEvent ( input_event )
end ,
SaveState = function ( )
self : sendEvent ( Event : new ( " FlushSettings " ) )
2014-08-26 10:10:26 +00:00
end ,
Power = function ( input_event )
Device : onPowerEvent ( input_event )
end ,
2014-06-26 17:37:33 +00:00
}
if Device : isKobo ( ) then
2014-08-26 10:10:26 +00:00
self.event_handlers [ " Suspend " ] = function ( input_event )
self : sendEvent ( Event : new ( " FlushSettings " ) )
Device : onPowerEvent ( input_event )
end
self.event_handlers [ " Resume " ] = function ( input_event )
Device : onPowerEvent ( input_event )
self : sendEvent ( Event : new ( " Resume " ) )
2014-06-26 17:37:33 +00:00
end
self.event_handlers [ " Light " ] = function ( )
Device : getPowerDevice ( ) : toggleFrontlight ( )
end
2014-06-26 18:28:57 +00:00
self.event_handlers [ " __default__ " ] = function ( input_event )
if Device.screen_saver_mode then
-- Suspension in Kobo can be interrupted by screen updates. We
-- ignore user touch input here so screen udpate won't be
-- triggered in suspend mode
return
else
self : sendEvent ( input_event )
end
end
2014-08-12 11:11:13 +00:00
if KOBO_LIGHT_ON_START and tonumber ( KOBO_LIGHT_ON_START ) > - 1 then
Device : getPowerDevice ( ) : setIntensity ( math.max ( math.min ( KOBO_LIGHT_ON_START , 100 ) , 0 ) )
end
2014-06-26 17:37:33 +00:00
elseif Device : isKindle ( ) then
self.event_handlers [ " IntoSS " ] = function ( )
self : sendEvent ( Event : new ( " FlushSettings " ) )
Device : intoScreenSaver ( )
end
self.event_handlers [ " OutOfSS " ] = function ( )
Device : outofScreenSaver ( )
2014-08-26 10:10:26 +00:00
self : sendEvent ( Event : new ( " Resume " ) )
2014-06-26 17:37:33 +00:00
end
self.event_handlers [ " Charging " ] = function ( )
Device : usbPlugIn ( )
end
self.event_handlers [ " NotCharging " ] = function ( )
Device : usbPlugOut ( )
self : sendEvent ( Event : new ( " NotCharging " ) )
2014-06-21 16:24:37 +00:00
end
2014-07-07 23:21:05 +00:00
-- Emulate the stock reader refresh behavior...
--[[
NOTE : For ref , on a Touch ( debugPaint is my new best friend ) :
UI : gc16_fast
Reader : When flash : if to / from img : gc16 , else gc16_fast ; when non - flash : auto ( seems to prefer gl16_fast ) ; Waiting for marker only on flash
On a PW2 :
Same as Touch , except reader uses reagl on non - flash , non - flash lasts longer ( 12 pgs ) ; Always waits for marker
--]]
-- We don't really have an easy way to know if we're refreshing the UI, or a page, or if said page contains an image, so go with the highest fidelity option
self.full_refresh_waveform_mode = WAVEFORM_MODE_GC16
-- We spend much more time in the reader than the UI, and our UI isn't very graphic anyway, so go with the reader behavior
2014-10-30 18:42:18 +00:00
if Device.model == " KindlePaperWhite2 " then
2014-07-07 23:21:05 +00:00
self.partial_refresh_waveform_mode = WAVEFORM_MODE_REAGL
else
self.partial_refresh_waveform_mode = WAVEFORM_MODE_GL16_FAST
-- NOTE: Or we could go back to what KOReader did before fa55acc in koreader-base, which was also use WAVEFORM_MODE_AUTO ;). I have *no* idea how the driver makes its choice though...
--self.partial_refresh_waveform_mode = WAVEFORM_MODE_AUTO
end
2014-06-21 16:24:37 +00:00
end
end
2012-04-22 19:29:48 +00:00
-- register & show a widget
2014-10-30 08:01:01 +00:00
-- modal widget should be always on the top
2012-04-22 19:29:48 +00:00
function UIManager : show ( widget , x , y )
2014-10-30 08:01:01 +00:00
DEBUG ( " show widget " , widget.id )
2014-09-03 03:13:46 +00:00
self._running = true
2014-10-30 08:01:01 +00:00
local window = { x = x or 0 , y = y or 0 , widget = widget }
-- put this window on top of the toppest non-modal window
for i = # self._window_stack , 0 , - 1 do
local top_window = self._window_stack [ i ]
-- skip modal window
if not top_window or not top_window.widget . modal then
table.insert ( self._window_stack , i + 1 , window )
break
end
end
2014-03-13 13:52:43 +00:00
-- and schedule it to be painted
self : setDirty ( widget )
-- tell the widget that it is shown now
widget : handleEvent ( Event : new ( " Show " ) )
-- check if this widget disables double tap gesture
if widget.disable_double_tap then
Input.disable_double_tap = true
end
2012-04-22 19:29:48 +00:00
end
-- unregister a widget
function UIManager : close ( widget )
2014-11-06 06:00:47 +00:00
if not widget then
DEBUG ( " widget not exist to be closed " )
return
end
2014-10-30 08:01:01 +00:00
DEBUG ( " close widget " , widget.id )
2014-03-13 13:52:43 +00:00
Input.disable_double_tap = DGESDETECT_DISABLE_DOUBLE_TAP
local dirty = false
for i = # self._window_stack , 1 , - 1 do
if self._window_stack [ i ] . widget == widget then
table.remove ( self._window_stack , i )
dirty = true
elseif self._window_stack [ i ] . widget.disable_double_tap then
Input.disable_double_tap = true
end
end
if dirty then
-- schedule remaining widgets to be painted
for i = 1 , # self._window_stack do
self : setDirty ( self._window_stack [ i ] . widget )
end
end
2012-04-22 19:29:48 +00:00
end
-- schedule an execution task
function UIManager : schedule ( time , action )
2014-03-13 13:52:43 +00:00
table.insert ( self._execution_stack , { time = time , action = action } )
2012-04-22 19:29:48 +00:00
end
-- schedule task in a certain amount of seconds (fractions allowed) from now
function UIManager : scheduleIn ( seconds , action )
2014-03-13 13:52:43 +00:00
local when = { util.gettime ( ) }
local s = math.floor ( seconds )
local usecs = ( seconds - s ) * 1000000
when [ 1 ] = when [ 1 ] + s
when [ 2 ] = when [ 2 ] + usecs
if when [ 2 ] > 1000000 then
when [ 1 ] = when [ 1 ] + 1
when [ 2 ] = when [ 2 ] - 1000000
end
self : schedule ( when , action )
2012-04-22 19:29:48 +00:00
end
-- register a widget to be repainted
2012-06-19 02:44:54 +00:00
function UIManager : setDirty ( widget , refresh_type )
2014-03-13 13:52:43 +00:00
-- "auto": request full refresh
-- "full": force full refresh
-- "partial": partial refresh
if not refresh_type then
refresh_type = " auto "
end
2014-08-20 06:45:11 +00:00
if widget then
self._dirty [ widget ] = refresh_type
end
2012-04-22 19:29:48 +00:00
end
2014-06-23 14:33:11 +00:00
function UIManager : insertZMQ ( zeromq )
table.insert ( self._zeromqs , zeromq )
return zeromq
end
function UIManager : removeZMQ ( zeromq )
for i = # self._zeromqs , 1 , - 1 do
if self._zeromqs [ i ] == zeromq then
table.remove ( self._zeromqs , i )
end
end
end
2014-07-26 14:22:52 +00:00
-- set full refresh rate for e-ink screen
-- and make the refresh rate persistant in global reader settings
function UIManager : setRefreshRate ( rate )
DEBUG ( " set screen full refresh rate " , rate )
self.FULL_REFRESH_COUNT = rate
G_reader_settings : saveSetting ( " full_refresh_count " , rate )
end
-- get full refresh rate for e-ink screen
function UIManager : getRefreshRate ( rate )
return self.FULL_REFRESH_COUNT
end
2012-04-22 19:29:48 +00:00
-- signal to quit
function UIManager : quit ( )
2014-10-30 08:01:01 +00:00
DEBUG ( " quit uimanager " )
2014-03-13 13:52:43 +00:00
self._running = false
2014-09-03 03:13:46 +00:00
for i = # self._window_stack , 1 , - 1 do
table.remove ( self._window_stack , i )
end
for i = # self._execution_stack , 1 , - 1 do
table.remove ( self._execution_stack , i )
end
2014-06-23 14:33:11 +00:00
for i = # self._zeromqs , 1 , - 1 do
self._zeromqs [ i ] : stop ( )
table.remove ( self._zeromqs , i )
end
2012-04-22 19:29:48 +00:00
end
-- transmit an event to registered widgets
function UIManager : sendEvent ( event )
2014-06-07 05:36:58 +00:00
if # self._window_stack == 0 then return end
2014-03-13 13:52:43 +00:00
-- top level widget has first access to the event
if self._window_stack [ # self._window_stack ] . widget : handleEvent ( event ) then
return
end
2012-04-22 19:29:48 +00:00
2014-03-13 13:52:43 +00:00
-- 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
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
end
end
2012-04-22 19:29:48 +00:00
end
2013-01-05 14:28:14 +00:00
function UIManager : checkTasks ( )
2014-03-13 13:52:43 +00:00
local now = { util.gettime ( ) }
2013-01-05 14:28:14 +00:00
2014-03-13 13:52:43 +00:00
-- check if we have timed events in our queue and search next one
local wait_until = nil
local all_tasks_checked
repeat
all_tasks_checked = true
for i = # self._execution_stack , 1 , - 1 do
local task = self._execution_stack [ i ]
if not task.time
or task.time [ 1 ] < now [ 1 ]
or task.time [ 1 ] == now [ 1 ] and task.time [ 2 ] < now [ 2 ] then
-- task is pending to be executed right now. do it.
task.action ( )
-- and remove from table
table.remove ( self._execution_stack , i )
-- start loop again, since new tasks might be on the
-- queue now
all_tasks_checked = false
elseif not wait_until
or wait_until [ 1 ] > task.time [ 1 ]
or wait_until [ 1 ] == task.time [ 1 ] and wait_until [ 2 ] > task.time [ 2 ] then
-- task is to be run in the future _and_ is scheduled
-- earlier than the tasks we looked at already
-- so adjust to the currently examined task instead.
wait_until = task.time
end
end
until all_tasks_checked
return wait_until
2013-01-05 14:28:14 +00:00
end
2012-04-22 19:29:48 +00:00
-- this is the main loop of the UI controller
-- it is intended to manage input events and delegate
-- them to dialogs
function UIManager : run ( )
2014-03-13 13:52:43 +00:00
self._running = true
while self._running do
local now = { util.gettime ( ) }
local wait_until = self : checkTasks ( )
2013-03-12 17:18:53 +00:00
2014-03-13 13:52:43 +00:00
--DEBUG("---------------------------------------------------")
--DEBUG("exec stack", self._execution_stack)
--DEBUG("window stack", self._window_stack)
--DEBUG("dirty stack", self._dirty)
--DEBUG("---------------------------------------------------")
2012-04-22 19:29:48 +00:00
2014-03-13 13:52:43 +00:00
-- stop when we have no window to show
if # self._window_stack == 0 then
2014-08-17 09:38:33 +00:00
DEBUG ( " no dialog left to show " )
2014-06-23 14:33:11 +00:00
self : quit ( )
2014-03-13 13:52:43 +00:00
return nil
end
2012-04-22 19:29:48 +00:00
2014-03-13 13:52:43 +00:00
-- repaint dirty widgets
local dirty = false
local request_full_refresh = false
local force_full_refresh = false
2014-07-06 19:38:13 +00:00
local force_partial_refresh = false
2014-03-13 13:52:43 +00:00
local force_fast_refresh = false
for _ , widget in ipairs ( self._window_stack ) do
2014-09-29 06:23:38 +00:00
-- paint if repaint_all is request
-- paint also if current widget or any widget underneath is dirty
if self.repaint_all or dirty or self._dirty [ widget.widget ] then
2014-10-30 18:42:18 +00:00
widget.widget : paintTo ( Screen.bb , widget.x , widget.y )
2014-03-13 13:52:43 +00:00
if self._dirty [ widget.widget ] == " auto " then
request_full_refresh = true
end
if self._dirty [ widget.widget ] == " full " then
force_full_refresh = true
end
if self._dirty [ widget.widget ] == " partial " then
2014-07-06 19:38:13 +00:00
force_partial_refresh = true
2014-03-13 13:52:43 +00:00
end
if self._dirty [ widget.widget ] == " fast " then
force_fast_refresh = true
end
-- and remove from list after painting
self._dirty [ widget.widget ] = nil
-- trigger repaint
dirty = true
end
end
2014-06-04 09:22:45 +00:00
2014-03-13 13:52:43 +00:00
if self.full_refresh then
dirty = true
force_full_refresh = true
end
2013-07-23 06:51:38 +00:00
2014-07-06 19:38:13 +00:00
if self.partial_refresh then
2014-03-13 13:52:43 +00:00
dirty = true
2014-07-06 19:38:13 +00:00
force_partial_refresh = true
2014-03-13 13:52:43 +00:00
end
2014-06-04 09:22:45 +00:00
2014-03-13 13:52:43 +00:00
self.repaint_all = false
self.full_refresh = false
2014-07-06 19:38:13 +00:00
self.partial_refresh = false
2014-06-04 09:22:45 +00:00
2014-03-13 13:52:43 +00:00
local refresh_type = self.default_refresh_type
local waveform_mode = self.default_waveform_mode
if dirty then
2014-07-06 19:38:13 +00:00
if force_partial_refresh or force_fast_refresh then
2014-03-13 13:52:43 +00:00
refresh_type = 0
elseif force_full_refresh or self.refresh_count == self.FULL_REFRESH_COUNT - 1 then
refresh_type = 1
end
2014-07-07 23:21:05 +00:00
-- Handle the waveform mode selection...
if refresh_type == 1 then
waveform_mode = self.full_refresh_waveform_mode
else
waveform_mode = self.partial_refresh_waveform_mode
2014-07-06 15:22:36 +00:00
end
2014-03-13 13:52:43 +00:00
if force_fast_refresh then
waveform_mode = self.fast_waveform_mode
end
2014-11-03 03:23:20 +00:00
if self.update_regions_func then
local update_regions = self.update_regions_func ( )
for _ , update_region in ipairs ( update_regions ) do
-- in some rare cases update region has 1 pixel offset
Screen : refresh ( refresh_type , waveform_mode ,
update_region.x - 1 , update_region.y - 1 ,
update_region.w + 2 , update_region.h + 2 )
end
2014-03-13 13:52:43 +00:00
else
Screen : refresh ( refresh_type , waveform_mode )
end
if self.refresh_type == 1 then
self.refresh_count = 0
2014-07-06 19:38:13 +00:00
elseif not force_partial_refresh and not force_full_refresh then
2014-03-13 13:52:43 +00:00
self.refresh_count = ( self.refresh_count + 1 ) % self.FULL_REFRESH_COUNT
end
2014-11-03 03:23:20 +00:00
self.update_regions_func = nil
2014-03-13 13:52:43 +00:00
end
2013-03-12 17:18:53 +00:00
2014-03-13 13:52:43 +00:00
self : checkTasks ( )
2013-03-12 17:18:53 +00:00
2014-03-13 13:52:43 +00:00
-- wait for next event
-- note that we will skip that if in the meantime we have tasks that are ready to run
local input_event = nil
if not wait_until then
2014-06-23 14:33:11 +00:00
if # self._zeromqs > 0 then
-- pending message queue, wait 100ms for input
input_event = Input : waitEvent ( 1000 * 100 )
2014-06-25 13:31:42 +00:00
if not input_event or input_event.handler == " onInputError " then
2014-06-23 14:33:11 +00:00
for _ , zeromq in ipairs ( self._zeromqs ) do
input_event = zeromq : waitEvent ( )
if input_event then break end
end
end
else
-- no pending task, wait endlessly
input_event = Input : waitEvent ( )
end
2014-03-13 13:52:43 +00:00
elseif wait_until [ 1 ] > now [ 1 ]
or wait_until [ 1 ] == now [ 1 ] and wait_until [ 2 ] > now [ 2 ] then
local wait_for = { s = wait_until [ 1 ] - now [ 1 ] , us = wait_until [ 2 ] - now [ 2 ] }
if wait_for.us < 0 then
wait_for.s = wait_for.s - 1
wait_for.us = 1000000 + wait_for.us
end
-- wait until next task is pending
input_event = Input : waitEvent ( wait_for.us , wait_for.s )
end
2012-04-22 19:29:48 +00:00
2014-03-13 13:52:43 +00:00
-- delegate input_event to handler
if input_event then
2014-06-26 17:37:33 +00:00
local handler = self.event_handlers [ input_event ]
if handler then
handler ( input_event )
2014-03-13 13:52:43 +00:00
else
2014-06-26 17:37:33 +00:00
self.event_handlers [ " __default__ " ] ( input_event )
2014-03-13 13:52:43 +00:00
end
end
end
2012-04-22 19:29:48 +00:00
end
2013-10-18 20:38:07 +00:00
2014-08-05 02:22:57 +00:00
function UIManager : getRefreshMenuTable ( )
local function custom_1 ( ) return G_reader_settings : readSetting ( " refresh_rate_1 " ) or 12 end
local function custom_2 ( ) return G_reader_settings : readSetting ( " refresh_rate_2 " ) or 22 end
local function custom_3 ( ) return G_reader_settings : readSetting ( " refresh_rate_3 " ) or 99 end
local function custom_input ( name )
return {
title = _ ( " Input page number for a full refresh " ) ,
type = " number " ,
hint = " (1 - 99) " ,
callback = function ( input )
2014-08-05 04:10:32 +00:00
local rate = tonumber ( input )
G_reader_settings : saveSetting ( name , rate )
UIManager : setRefreshRate ( rate )
2014-08-05 02:22:57 +00:00
end ,
}
end
return {
text = _ ( " E-ink full refresh rate " ) ,
sub_item_table = {
{
text = _ ( " Every page " ) ,
checked_func = function ( ) return UIManager : getRefreshRate ( ) == 1 end ,
callback = function ( ) UIManager : setRefreshRate ( 1 ) end ,
} ,
{
text = _ ( " Every 6 pages " ) ,
checked_func = function ( ) return UIManager : getRefreshRate ( ) == 6 end ,
callback = function ( ) UIManager : setRefreshRate ( 6 ) end ,
} ,
{
text_func = function ( ) return _ ( " Custom " ) .. " 1: " .. custom_1 ( ) .. _ ( " pages " ) end ,
checked_func = function ( ) return UIManager : getRefreshRate ( ) == custom_1 ( ) end ,
callback = function ( ) UIManager : setRefreshRate ( custom_1 ( ) ) end ,
hold_input = custom_input ( " refresh_rate_1 " )
} ,
{
text_func = function ( ) return _ ( " Custom " ) .. " 2: " .. custom_2 ( ) .. _ ( " pages " ) end ,
checked_func = function ( ) return UIManager : getRefreshRate ( ) == custom_2 ( ) end ,
callback = function ( ) UIManager : setRefreshRate ( custom_2 ( ) ) end ,
hold_input = custom_input ( " refresh_rate_2 " )
} ,
{
text_func = function ( ) return _ ( " Custom " ) .. " 3: " .. custom_3 ( ) .. _ ( " pages " ) end ,
checked_func = function ( ) return UIManager : getRefreshRate ( ) == custom_3 ( ) end ,
callback = function ( ) UIManager : setRefreshRate ( custom_3 ( ) ) end ,
hold_input = custom_input ( " refresh_rate_3 " )
} ,
}
}
end
2014-06-26 17:37:33 +00:00
UIManager : init ( )
2013-10-18 20:38:07 +00:00
return UIManager
2014-06-21 16:24:37 +00:00