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:shiftScheduledTasksBy (time) Shift the execution times of all scheduled tasks.
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:getNthTopWidget (n) Get n.th topmost widget
UIManager:topdown_widgets_iter ()

Top-to-bottom widgets iterator NOTE: VirtualKeyboard can be instantiated multiple times, and is a modal,

    so don't be surprised if you find a couple of instances of it at the top ;).
UIManager:getTopmostVisibleWidget () Get the topmost visible widget
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:quit (exit_code, implicit) Signals to quit.
UIManager:sendEvent (event) Transmits an Event to active widgets, top to bottom.
UIManager:broadcastEvent (event) Transmits an Event to all registered widgets.
UIManager:getTime () Returns a time (fts) corresponding to the last tick.
UIManager:getElapsedTimeSinceBoot () Returns a time (fts) corresponding to the last UI tick plus the time in standby.
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: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-fixme7 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]", "partial", "ui", "fast", "a2" (optional)
  • refreshregion a rectangle Geom object (optional, requires refreshtype to be set)
  • x integer horizontal screen offset (optional, 0 if omitted)
  • y integer vertical screen offset (optional, 0 if omitted)
  • refreshdither boolean 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]", "partial", "ui", "fast", "a2" (optional)
  • refreshregion a rectangle Geom object (optional, requires refreshtype to be set)
  • refreshdither boolean true if the refresh requires dithering (optional, requires refreshtype to be set)

See also:

UIManager:shiftScheduledTasksBy (time)
Shift the execution times of all scheduled tasks. UIManager uses CLOCK_MONOTONIC (which doesn't tick during standby), so shifting the execution time by a negative value will lead to an execution at the expected time.

Parameters:

  • time if positive execute the tasks later, if negative they should be executed earlier
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 function 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 function 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 function reference to the task to be scheduled (may be anonymous)
  • ... optional arguments passed to action

Returns:

    A reference to the initial nextTick wrapper function, necessary if the caller wants to unschedule action before it actually gets inserted in the task queue by nextTick.

See also:

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

In order to unschedule anonymous functions, store a reference.

Parameters:

  • action function

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.
  • [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). Should apply to most UI elements. When in doubt, use this.
  • [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. 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).
  • 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. 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]", "partial", "ui", "fast", "a2" (or a lambda, see description above)
  • refreshregion a rectangle Geom object (optional, omitting it means the region will cover the full screen)
  • refreshdither boolean 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 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:getNthTopWidget (n)
Get n.th topmost widget

Parameters:

  • n
UIManager:topdown_widgets_iter ()

Top-to-bottom widgets iterator NOTE: VirtualKeyboard can be instantiated multiple times, and is a modal,

    so don't be surprised if you find a couple of instances of it at the top ;).
UIManager:getTopmostVisibleWidget ()
Get the topmost visible widget
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:quit (exit_code, implicit)
Signals to quit. An exit_code of false is not allowed.

Parameters:

  • exit_code
  • implicit
UIManager:sendEvent (event)
Transmits an Event to active widgets, top to bottom. Stops at the first handler that returns true. Note that most complex widgets are based on ???, which itself will take care of propagating an event to its members.

Parameters:

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

Parameters:

UIManager:getTime ()
Returns a time (fts) corresponding to the last tick.

This is essentially a cached time.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, assuming the UI is in use (e.g., there are input events), unless you're blocking the UI for a significant amount of time in a single UI frame.

That is to say, its granularity is an UI frame.

Prefer the appropriate time function for your needs if you require perfect accuracy or better granularity (e.g., when you're actually working on the event loop itself (UIManager, Input, GestureDetector), or if you're dealing with intra-frame timers).

This is NOT wall clock time (REALTIME).

UIManager:getElapsedTimeSinceBoot ()
Returns a time (fts) corresponding to the last UI tick plus the time in standby.
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 integer 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 integer left origin of widget (in the Screen buffer, e.g., widget.dimen.x)
  • y integer 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 integer left origin of the rectangle to invert (in the Screen buffer, e.g., widget.dimen.x)
  • y integer top origin of the rectangle (in the Screen buffer, e.g., widget.dimen.y)
  • w integer width of the rectangle (optional, will use widget.dimen.w like paintTo would if omitted)
  • h integer 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: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-fixme7
Force close looper when there is unhandled error, otherwise the looper will hang. Any better solution?
generated by LDoc 1.5.0 Last updated 2024-11-21 07:57:34