To quit the message loop, you can call the lifetime.quit() API, which would
break the blocking lifetime.run() call and continue the script, usually you
should exit the process after that.

Note that this hack does not work perfectly, you should never use it in
production.

Yode

To solve the problem of incompatible event loop, you can use the
Yode project as a replacement of Node.js. Yode
is a fork of Node.js that replaces libuv event loop with native GUI message
loops, so there would be no need to use the lifetime.run() hack.

Unlike Node.js which would quit the process when there is no work to do, the
processes of Yode would keep running forever, until you call the
lifetime.quit() API to quit current message loop.

After quitting the GUI message loop, the libuv event loop is still running, and
the process will exit when all pending Node.js requests have finished.

The code example above also showed how to make the script run under both Yode
and Node.js.

Why other GUI toolkit bindings do not work

Having read so far, you might have understood why people were not using Node.js
for native desktop apps. This was because the design of Node.js natually does
not allow integrating the GUI message loops of native toolkits.

The only exeptions here are GTK+ and other X11 based toolkits, because
internally they use file descriptor based GUI message loops and can be iterated
with libuv.

So even though it is not hard to write V8 bindings for Cocoa or Qt, it is
impossible to run their message loops together with the event loop of Node.js.
The most common trick of keep iterating events of GUI message loops, results in
high CPU usage. While the trick used by Yue's Lifetime API to replace the
event loop, has various problems with the events queue of Node.js.

Luckily with Yode the problem with message loop has been solved cleanly, even if
you are not intersted in Yue, it is still possible to use Win32 and Cocoa
bindings in Yode.

Electron

Since the main process of Electron uses GUI message loops, there is no need to
use the Lifetime API, and the Lifetime API of Yue is not available when
running under Electron. You should always use the app API of Electron to
manage the process's lifetime.

Creating a window

Each creatable type in Yue has a create class method can be used to create
instances of the type, constructors are not used because JavaScript does not
support function overloading while certain types can have multiple createXXX
class methods.

const win = gui.Window.create({})

MenuBar

With gui.MenuBar and gui.MenuItem APIs you can create menu bars, their
create methods also accept object descriptors to make the APIs easier to use.

By adding a menu bar, you can bind keyboard shortcuts to actions, and for some
very common actions there are also stock items can be used.

Note that macOS differs from other platforms that it has one application menu
instead of window menu bars, so your code should be aware of this difference.

Content view

Each window in Yue has one content view, which fills the client area of the
window.

const edit = gui.TextEdit.create()
win.setContentView(edit)

Container and layout

The Container view can have multiple views and it can automatically layout
the child views according to the flexbox style properties assigned.

Following code creates a vertical sidebar on the left of the text edit view,
the sidebar stretches vertically and takes fixed width, while the text edit view
would fill all remaining space.

// The content view has its children arranged horizontally.const contentView = gui.Container.create()
contentView.setStyle({flexDirection: 'row'})
win.setContentView(contentView)
// The sidebar is a child of content view and has 5px paddings.const sidebar = gui.Container.create()
sidebar.setStyle({padding: 5})
contentView.addChildView(sidebar)
// Make the sidebar have a fixed width which is enough to show all the buttons.
sidebar.setStyle({width: sidebar.getPreferredSize().width})
// The text edit view would take all remaining spaces.const edit = gui.TextEdit.create()
edit.setStyle({flex: 1})
contentView.addChildView(edit)

Vibrant view

On macOS views can be semi-transparent to show contents under the window, our
example makes use of this by using the Vibrant view for sidebar.