* feat: backport js_gpio from unleashed * feat: backport js_keyboard, TextInputModel::minimum_length from unleashed * fix: api version inconsistency * style: js_gpio * build: fix submodule ._ . * refactor: js_gpio * docs: type declarations for gpio * feat: gpio interrupts * fix: js_gpio freeing, resetting and minor stylistic changes * style: js_gpio * style: mlib array, fixme's * feat: js_gpio adc * feat: js_event_loop * docs: js_event_loop * feat: js_event_loop subscription cancellation * feat: js_event_loop + js_gpio integration * fix: js_event_loop memory leak * feat: stop event loop on back button * test: js: basic, math, event_loop * feat: js_event_loop queue * feat: js linkage to previously loaded plugins * build: fix ci errors * feat: js module ordered teardown * feat: js_gui_defer_free * feat: basic hourglass view * style: JS ASS (Argument Schema for Scripts) * fix: js_event_loop mem leaks and lifetime problems * fix: crashing test and pvs false positives * feat: mjs custom obj destructors, gui submenu view * refactor: yank js_gui_defer_free (yuck) * refactor: maybe_unsubscribe * empty_screen, docs, typing fix-ups * docs: navigation event & demo * feat: submenu setHeader * feat: text_input * feat: text_box * docs: text_box availability * ci: silence irrelevant pvs low priority warning * style: use furistring * style: _get_at -> _safe_get * fix: built-in module name assignment * feat: js_dialog; refactor, optimize: js_gui * docs: js_gui * ci: silence pvs warning: Memory allocation is infallible * style: fix storage spelling * feat: foreign pointer signature checks * feat: js_storage * docs: js_storage * fix: my unit test was breaking other tests ;_; * ci: fix ci? * Make doxygen happy * docs: flipper, math, notification, global * style: review suggestions * style: review fixups * fix: badusb demo script * docs: badusb * ci: add nofl * ci: make linter happy * Bump api version Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
5.8 KiB
js_gui
GUI module
let eventLoop = require("event_loop");
let gui = require("gui");
This module depends on the event_loop module, so it must only be imported
after event_loop is imported.
Conceptualizing GUI
Event loop
It is highly recommended to familiarize yourself with the event loop first before doing GUI-related things.
Canvas
The canvas is just a drawing area with no abstractions over it. Drawing on the canvas directly (i.e. not through a viewport) is useful in case you want to implement a custom design element, but this is rather uncommon.
Viewport
A viewport is a window into a rectangular portion of the canvas. Applications always access the canvas through a viewport.
View
In Flipper's terminology, a "View" is a fullscreen design element that assumes control over the entire viewport and all input events. Different types of views are available (not all of which are unfortunately currently implemented in JS):
| View | Has JS adapter? |
|---|---|
button_menu |
❌ |
button_panel |
❌ |
byte_input |
❌ |
dialog_ex |
✅ (as dialog) |
empty_screen |
✅ |
file_browser |
❌ |
loading |
✅ |
menu |
❌ |
number_input |
❌ |
popup |
❌ |
submenu |
✅ |
text_box |
✅ |
text_input |
✅ |
variable_item_list |
❌ |
widget |
❌ |
In JS, each view has its own set of properties (or just "props"). The programmer can manipulate these properties in two ways:
- Instantiate a
Viewusing themakeWith(props)method, passing an object with the initial properties - Call
set(name, value)to modify a property of an existingView
View Dispatcher
The view dispatcher holds references to all the views that an application needs and switches between them as the application makes requests to do so.
Scene Manager
The scene manager is an optional add-on to the view dispatcher that makes managing applications with complex navigation flows easier. It is currently inaccessible from JS.
Approaches
In total, there are three different approaches that you may take when writing a GUI application:
| Approach | Use cases | Available from JS |
|---|---|---|
| ViewPort only | Accessing the graphics API directly, without any of the nice UI abstractions | ❌ |
| ViewDispatcher | Common UI elements that fit with the overall look of the system | ✅ |
| SceneManager | Additional navigation flow management for complex applications | ❌ |
Example
An example with three different views using the ViewDispatcher approach:
let eventLoop = require("event_loop");
let gui = require("gui");
let loadingView = require("gui/loading");
let submenuView = require("gui/submenu");
let emptyView = require("gui/empty_screen");
// Common pattern: declare all the views in an object. This is absolutely not
// required, but adds clarity to the script.
let views = {
// the view dispatcher auto-✨magically✨ remembers views as they are created
loading: loadingView.make(),
empty: emptyView.make(),
demos: submenuView.makeWith({
items: [
"Hourglass screen",
"Empty screen",
"Exit app",
],
}),
};
// go to different screens depending on what was selected
eventLoop.subscribe(views.demos.chosen, function (_sub, index, gui, eventLoop, views) {
if (index === 0) {
gui.viewDispatcher.switchTo(views.loading);
} else if (index === 1) {
gui.viewDispatcher.switchTo(views.empty);
} else if (index === 2) {
eventLoop.stop();
}
}, gui, eventLoop, views);
// go to the demo chooser screen when the back key is pressed
eventLoop.subscribe(gui.viewDispatcher.navigation, function (_sub, _, gui, views) {
gui.viewDispatcher.switchTo(views.demos);
}, gui, views);
// run UI
gui.viewDispatcher.switchTo(views.demos);
eventLoop.run();
API reference
viewDispatcher
The viewDispatcher constant holds the ViewDispatcher singleton.
viewDispatcher.switchTo(view)
Switches to a view, giving it control over the display and input
Parameters
view: theViewto switch to
viewDispatcher.sendTo(direction)
Sends the viewport that the dispatcher manages to the front of the stackup (effectively making it visible), or to the back (effectively making it invisible)
Parameters
direction: either"front"or"back"
viewDispatcher.sendCustom(event)
Sends a custom number to the custom event handler
Parameters
event: number to send
viewDispatcher.custom
An event loop Contract object that identifies the custom event source,
triggered by ViewDispatcher.sendCustom(event)
viewDispatcher.navigation
An event loop Contract object that identifies the navigation event source,
triggered when the back key is pressed
ViewFactory
When you import a module implementing a view, a ViewFactory is instantiated.
For example, in the example above, loadingView, submenuView and emptyView
are view factories.
ViewFactory.make()
Creates an instance of a View
ViewFactory.make(props)
Creates an instance of a View and assigns initial properties from props
Parameters
props: simple key-value object, e.g.{ header: "Header" }