Module ui.uimanager

This module manages widgets.

Functions

UIManager:show (widget, refreshtype, refreshregion, x, y, refreshdither) Registers and shows a widget.
UIManager:close (widget, refreshtype, refreshregion, refreshdither) Unregisters a widget.
UIManager:scheduleIn (seconds, action, ...) Schedules a task to be run a certain amount of seconds from now.
UIManager:nextTick (action, ...) Schedules a task for the next UI tick.
UIManager:tickAfterNext (action, ...) Schedules a task to be run two UI ticks from now.
UIManager:unschedule (action) Unschedules a previously scheduled task.
UIManager:setDirty (widget, refreshtype, refreshregion, refreshdither) Mark a window-level widget as dirty, enqueuing a repaint & refresh request for that widget, to be processed on the next UI tick.
UIManager:clearRenderStack () Clear the full repaint & refresh queues.
UIManager:setRefreshRate (rate, night_rate) Sets the full refresh rate for e-ink screens (FULL_REFRESH_COUNT).
UIManager:getRefreshRate () Returns the full refresh rate for e-ink screens (FULL_REFRESH_COUNT).
UIManager:ToggleNightMode (night_mode) Toggles Night Mode (i.e., inverted rendering).
UIManager:getTopWidget () Get top widget (name if possible, ref otherwise).
UIManager:getSecondTopmostWidget () Get the second topmost widget, if there is one (name if possible, ref otherwise).
UIManager:isSubwidgetShown (widget, max_depth) Check if a widget is still in the window stack, or is a subwidget of a widget still in the window stack.
UIManager:isWidgetShown (widget) Same as isSubwidgetShown, but only check window-level widgets (e.g., what's directly registered in the window stack), don't recurse.
UIManager:getPreviousRefreshRegion () Returns the region of the previous refresh.
UIManager:quit () Signals to quit.
UIManager:discardEvents (set_or_seconds) Request all Events to be ignored for some duration.
UIManager:sendEvent (event) Transmits an Event to active widgets.
UIManager:broadcastEvent (event) Transmits an Event to all registered widgets.
UIManager:getTime () Returns a TimeVal object corresponding to the last UI tick.
UIManager:forceRePaint () Explicitly drain the paint & refresh queues now, instead of waiting for the next UI tick.
UIManager:waitForVSync () Ask the EPDC to block until our previous refresh ioctl has completed.
UIManager:yieldToEPDC (sleep_us) Yield to the EPDC.
UIManager:widgetRepaint (widget, x, y) Used to repaint a specific sub-widget that isn't on the _window_stack itself.
UIManager:widgetInvert (widget, x, y, w, h) Same idea as widgetRepaint, but does a simple bb:invertRect on the Screen buffer, without actually going through the widget's paintTo method.
UIManager:run () This is the main loop of the UI controller.
UIManager:suspend () Executes all the operations of a suspension (i.e., sleep) request.
UIManager:resume () Executes all the operations of a resume (i.e., wakeup) request.
UIManager:allowStandby () Release standby lock.
UIManager:preventStandby () Prevent standby.
UIManager:flushSettings () Broadcasts a FlushSettings Event to all widgets.
UIManager:restartKOReader () Sanely restart KOReader (on supported platforms).
UIManager:abort () Sanely abort KOReader (e.g., exit sanely, but with a non-zero return code).

Issues

UIManager:widgetInvert-fixme12 Force close looper when there is unhandled error, otherwise the looper will hang. Any better solution?


Functions

UIManager:show (widget, refreshtype, refreshregion, x, y, refreshdither)
Registers and shows a widget.

Widgets are registered in a stack, from bottom to top in registration order, with a few tweaks to handle modals & toasts: toast widgets are stacked together on top, then modal widgets are stacked together, and finally come standard widgets.

If you think about how painting will be handled (also bottom to top), this makes perfect sense ;).

For more details about refreshtype, refreshregion & refreshdither see the description of setDirty. If refreshtype is omitted, no refresh will be enqueued at this time.

Parameters:

  • widget a widget object
  • refreshtype string "full", "flashpartial", "flashui", "partial", "ui", "fast" (optional)
  • refreshregion a rectangle Geom object (optional, requires refreshtype to be set)
  • x int horizontal screen offset (optional, 0 if omitted)
  • y int vertical screen offset (optional, 0 if omitted)
  • refreshdither bool true if widget requires dithering (optional, requires refreshtype to be set)

See also:

UIManager:close (widget, refreshtype, refreshregion, refreshdither)
Unregisters a widget.

It will be removed from the stack. Will flag uncovered widgets as dirty.

For more details about refreshtype, refreshregion & refreshdither see the description of setDirty. If refreshtype is omitted, no extra refresh will be enqueued at this time, leaving only those from the uncovered widgets.

Parameters:

  • widget a widget object
  • refreshtype string "full", "flashpartial", "flashui", "partial", "ui", "fast" (optional)
  • refreshregion a rectangle Geom object (optional, requires refreshtype to be set)
  • refreshdither bool true if the refresh requires dithering (optional, requires refreshtype to be set)

See also:

UIManager:scheduleIn (seconds, action, ...)
Schedules a task to be run a certain amount of seconds from now.

Parameters:

  • seconds number scheduling delay in seconds (supports decimal values, 1ms resolution).
  • action func reference to the task to be scheduled (may be anonymous)
  • ... optional arguments passed to action

See also:

UIManager:nextTick (action, ...)
Schedules a task for the next UI tick.

Parameters:

  • action func reference to the task to be scheduled (may be anonymous)
  • ... optional arguments passed to action

See also:

UIManager:tickAfterNext (action, ...)
Schedules a task to be run two UI ticks from now.

Useful to run UI callbacks ASAP without skipping repaints.

Parameters:

  • action func reference to the task to be scheduled (may be anonymous)
  • ... optional arguments passed to action

See also:

UIManager:unschedule (action)
Unschedules a previously scheduled task.

In order to unschedule anonymous functions, store a reference.

Parameters:

  • action func

See also:

Usage:

    self.anonymousFunction = function() self:regularFunction() end
    UIManager:scheduleIn(10.5, self.anonymousFunction)
    UIManager:unschedule(self.anonymousFunction)
UIManager:setDirty (widget, refreshtype, refreshregion, refreshdither)
Mark a window-level widget as dirty, enqueuing a repaint & refresh request for that widget, to be processed on the next UI tick.

The second parameter (refreshtype) can either specify a refreshtype (optionally in combination with a refreshregion - which is suggested, and an even more optional refreshdither flag if the content requires dithering); or a function that returns a refreshtype, refreshregion tuple (or a refreshtype, refreshregion, refreshdither triple), which will be called after painting the widget. This is an interesting distinction, because a widget's geometry, usually stored in a field named dimen, is (generally) only computed at painting time (e.g., during paintTo). The TL;DR being: if you already know the region, you can pass everything by value directly, (it'll make for slightly more readable debug logs), but if the region will only be known after the widget has been painted, pass a function. Note that, technically, it means that stuff passed by value will be enqueued earlier in the refresh stack. In practice, since the stack of (both types of) refreshes is optimized into as few actual refresh ioctls as possible, and that during the next _repaint tick (which is when paintTo for dirty widgets happens), this shouldn't change much in the grand scheme of things, but it ought to be noted ;).

See _repaint for more details about how the repaint & refresh queues are processed, and handleInput for more details about when those queues are actually drained. What you should essentially remember is that setDirty doesn't actually "do" anything visible on its own. It doesn't block, and when it returns, nothing new has actually been painted or refreshed. It just appends stuff to the paint and/or refresh queues.

Here's a quick rundown of what each refreshtype should be used for:

  • full: high-fidelity flashing refresh (e.g., large images). Highest quality, but highest latency. Don't abuse if you only want a flash (in this case, prefer flashui or flashpartial).
  • partial: medium fidelity refresh (e.g., text on a white background). Can be promoted to flashing after FULL\_REFRESH\_COUNT refreshes. Don't abuse to avoid spurious flashes. In practice, this means this should mostly always be limited to ReaderUI.
  • ui: medium fidelity refresh (e.g., mixed content). Should apply to most UI elements. When in doubt, use this.
  • fast: low fidelity refresh (e.g., monochrome content). Should apply to most highlighting effects achieved through inversion. Note that if your highlighted element contains 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).
  • flashui: like ui, but flashing. 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). Can be used when closing an UI element (usually over ReaderUI), to avoid ghosting. You can even drop the region in these cases, to ensure a fullscreen flash. NOTE: On REAGL devices, flashpartial will NOT actually flash (by design). As such, even onCloseWidget, you might prefer flashui in most instances.

NOTE: You'll notice a trend on UI elements that are usually shown over some kind of text (generally ReaderUI) of using "ui" onShow & onUpdate, but "partial" onCloseWidget. This is by design: "partial" is what the reader (ReaderUI) uses, as it's tailor-made for pure text over a white background, so this ensures we resume the usual flow of the reader. The same dynamic is true for their flashing counterparts, in the rare instances we enforce flashes. Any kind of "partial" refresh will count towards a flashing promotion after FULL_REFRESH_COUNT refreshes, so making sure your stuff only applies to the proper region is key to avoiding spurious large black flashes. That said, depending on your use case, using "ui" onCloseWidget can be a perfectly valid decision, and will ensure never seeing a flash because of that widget. Remember that the FM uses "ui", so, if said widgets are shown over the FM, prefer using "ui" or "flashui" onCloseWidget.

The final parameter (refreshdither) is an optional hint for devices with hardware dithering support that this repaint could benefit from dithering (e.g., because it contains an image).

As far as the actual lifecycle of a widget goes, the rules are:

  • What you show, you close.
  • If you know the dimensions of the widget (or simply of the region you want to refresh), you can pass it directly:
    • to show (as show calls setDirty),
    • to close (as close will also call setDirty on the remaining dirty and visible widgets, and will also enqueue a refresh based on that if there are dirty widgets).
  • Otherwise, you can use, respectively, a widget's Show & CloseWidget handlers for that via setDirty calls. This can also be useful if child widgets have specific needs (e.g., flashing, dithering) that they want to inject in the refresh queue.
  • Remember that events propagate children first (in array order, starting at the top), and that if any event handler returns true, the propagation of that specific event for this widget tree stops immediately. (This generally means that, unless you know what you're doing (e.g., a widget that will always be used as a parent), you generally don't want to return true in Show or CloseWidget handlers).
  • If any widget requires freeing non-Lua resources (e.g., FFI/C), having a free method called from its CloseWidget handler is ideal: this'll ensure that any widget including it will be sure that resources are freed when it (or its parent) are closed.
  • Note that there is a Close event, but it has very specific use-cases, generally involving programmatically closeing a shown widget:
    • It is broadcast (e.g., sent to every widget in the window stack; the same rules about propagation apply, but only per window-level widget) at poweroff/reboot.
    • It can also be used as a keypress handler by InputContainer, generally bound to the Back key.

Please refrain from implementing custom onClose methods if that's not their intended purpose ;).

On the subject of widgets and child widgets, you might have noticed an unspoken convention across the codebase of widgets having a field called show_parent. Since handling this is entirely at the programmer's behest, here's how we usually use it: Basically, we cascade a field named show_parent to every child widget that matter (e.g., those that serve an UI purpose, as opposed to, say, a container). This ensures that every subwidget can reference its actual parent (ideally, all the way to the window-level widget it belongs to, i.e., the one that was passed to show, hence the name ;)), to, among other things, flag the right widget for repaint via setDirty (c.f., those pesky debug warnings when that's done wrong ;p) when they want to request a repaint. This is why you often see stuff doing, when instantiating a new widget, FancyWidget:new{ show_parent = self.show_parent or self }; meaning, if I'm already a subwidget, cascade my parent, otherwise, it means I'm a window-level widget, so cascade myself as that widget's parent ;).

Another convention (that a few things rely on) is naming a (persistent) MovableContainer wrapping a full widget movable, accessible as an instance field. This is useful when it's used for transparency purposes, which, e.g., setDirty and Button rely on to handle updating translucent widgets properly, by checking if self.show_parent.movable exists and is currently translucent ;).

When I mentioned passing the right widget to setDirty earlier, what I meant is that setDirty will only actually flag a widget for repaint if that widget is a window-level widget (that is, a widget that was passed to show earlier and hasn't been close'd yet), hence the self.show_parent convention detailed above to get at the proper widget from within a subwidget ;). Otherwise, you'll notice in debug mode that a debug guard will shout at you if that contract is broken, and what happens in practice is the same thing as if an explicit nil were passed: no widgets will actually be flagged for repaint, and only the refresh matching the requested region will be enqueued. This is why you'll find a number of valid use-cases for passing a nil here, when you just want a screen refresh without a repaint :). The string "all" is also accepted in place of a widget, and will do the obvious thing: flag the full window stack, bottom to top, for repaint, while still honoring the refresh region (e.g., this doesn't enforce a full-screen refresh).

Parameters:

  • widget a window-level widget object, "all", or nil
  • refreshtype "full", "flashpartial", "flashui", "partial", "ui", "fast" (or a lambda, see description above)
  • refreshregion a rectangle Geom object (optional, omitting it means the region will cover the full screen)
  • refreshdither bool true if widget requires dithering (optional)

Usage:

    UIManager:setDirty(self.widget, "partial")
    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:clearRenderStack ()
Clear the full repaint & refresh queues.

NOTE: Beware! This doesn't take any prisonners! You shouldn't have to resort to this unless in very specific circumstances! plugins/coverbrowser.koplugin/covermenu.lua building a franken-menu out of buttondialogtitle & buttondialog and wanting to avoid inheriting their original paint/refresh cycle being a prime example.

UIManager:setRefreshRate (rate, night_rate)
Sets the full refresh rate for e-ink screens (FULL_REFRESH_COUNT).

This is the amount of "partial" refreshes before the next one gets promoted to "full".

Also makes the refresh rate persistent in global reader settings.

Parameters:

  • rate
  • night_rate

See also:

UIManager:getRefreshRate ()
Returns the full refresh rate for e-ink screens (FULL_REFRESH_COUNT).
UIManager:ToggleNightMode (night_mode)
Toggles Night Mode (i.e., inverted rendering).

Parameters:

  • night_mode
UIManager:getTopWidget ()
Get top widget (name if possible, ref otherwise).
UIManager:getSecondTopmostWidget ()
Get the second topmost widget, if there is one (name if possible, ref otherwise).

Useful when VirtualKeyboard is involved, as it always steals the top spot ;).

NOTE: Will skip over VirtualKeyboard instances, plural, in case there are multiple (because, apparently, we can do that.. ugh).

UIManager:isSubwidgetShown (widget, max_depth)
Check if a widget is still in the window stack, or is a subwidget of a widget still in the window stack.

Parameters:

  • widget
  • max_depth
UIManager:isWidgetShown (widget)
Same as isSubwidgetShown, but only check window-level widgets (e.g., what's directly registered in the window stack), don't recurse.

Parameters:

  • widget
UIManager:getPreviousRefreshRegion ()
Returns the region of the previous refresh.

Returns:

    a rectangle Geom object
UIManager:quit ()
Signals to quit.
UIManager:discardEvents (set_or_seconds)
Request all Events to be ignored for some duration.

Parameters:

  • set_or_seconds either true, in which case a platform-specific delay is chosen, or a duration in seconds (int).
UIManager:sendEvent (event)
Transmits an Event to active widgets.

Parameters:

UIManager:broadcastEvent (event)
Transmits an Event to all registered widgets.

Parameters:

UIManager:getTime ()
Returns a TimeVal object corresponding to the last UI tick.

This is essentially a cached TimeVal:now(), computed at the top of every iteration of the main UI loop, (right before checking/running scheduled tasks). This is mainly useful to compute/schedule stuff in the same time scale as the UI loop (i.e., MONOTONIC), without having to resort to a syscall. It should never be significantly stale (i.e., it should be precise enough), unless you're blocking the UI for a significant amount of time in the same UI tick.

Prefer the appropriate TimeVal method for your needs if you require perfect accuracy (e.g., when you're actually working on the event loop itself (UIManager, Input, GestureDetector)).

This is NOT wall clock time (REALTIME).

UIManager:forceRePaint ()
Explicitly drain the paint & refresh queues now, instead of waiting for the next UI tick.
UIManager:waitForVSync ()
Ask the EPDC to block until our previous refresh ioctl has completed.

This interacts sanely with the existing low-level handling of this in framebuffer_mxcfb (i.e., it doesn't even try to wait for a marker that fb has already waited for, and vice-versa).

Will return immediately if it has already completed.

If the device isn't a Linux + MXCFB device, this is a NOP.

UIManager:yieldToEPDC (sleep_us)
Yield to the EPDC.

This is a dumb workaround for potential races with the EPDC when we request a refresh on a specific region, and then proceed to write to the framebuffer, in the same region, very, very, very soon after that.

This basically just puts ourselves to sleep for a very short amount of time, to let the kernel do its thing in peace.

Parameters:

  • sleep_us int Amount of time to sleep for (in ┬Ás). (Optional, defaults to 1ms).
UIManager:widgetRepaint (widget, x, y)
Used to repaint a specific sub-widget that isn't on the _window_stack itself.

Useful to avoid repainting a complex widget when we just want to invert an icon, for instance. No safety checks on x & y by design. I want this to blow up if used wrong.

This is an explicit repaint now: it bypasses and ignores the paint queue (unlike setDirty).

Parameters:

  • widget a widget object
  • x int left origin of widget (in the Screen buffer, e.g., widget.dimen.x)
  • y int top origin of widget (in the Screen buffer, e.g., widget.dimen.y)
UIManager:widgetInvert (widget, x, y, w, h)
Same idea as widgetRepaint, but does a simple bb:invertRect on the Screen buffer, without actually going through the widget's paintTo method.

Parameters:

  • widget a widget object
  • x int left origin of the rectangle to invert (in the Screen buffer, e.g., widget.dimen.x)
  • y int top origin of the rectangle (in the Screen buffer, e.g., widget.dimen.y)
  • w int width of the rectangle (optional, will use widget.dimen.w like paintTo would if omitted)
  • h int height of the rectangle (optional, will use widget.dimen.h like paintTo would if omitted)

See also:

UIManager:run ()
This is the main loop of the UI controller.

It is intended to manage input events and delegate them to dialogs.

UIManager:suspend ()
Executes all the operations of a suspension (i.e., sleep) request.

This function usually puts the device into suspension.

UIManager:resume ()
Executes all the operations of a resume (i.e., wakeup) request.

This function usually wakes up the device.

UIManager:allowStandby ()
Release standby lock.

Called once we're done with whatever we were doing in the background. Standby is re-enabled only after all issued prevents are paired with allowStandby for each one.

UIManager:preventStandby ()
Prevent standby.

i.e., something is happening in background, yet UI may tick.

UIManager:flushSettings ()
Broadcasts a FlushSettings Event to all widgets.
UIManager:restartKOReader ()
Sanely restart KOReader (on supported platforms).
UIManager:abort ()
Sanely abort KOReader (e.g., exit sanely, but with a non-zero return code).

Issues

UIManager:widgetInvert-fixme12
Force close looper when there is unhandled error, otherwise the looper will hang. Any better solution?
generated by LDoc 1.4.6 Last updated 2021-04-21 20:45:58