User Interface for libplatform Applications
In the first post, we discussed libplatform
and package
, two major components that provide the ability to build multi-platform same-binary applications. In this post, we will consider a cross platform graphical user interface.
Traditional Native GUIs
Graphical User Interface (GUI) implementation remains a significant portion of the OS-specific code in a native application. Each OS has it’s primary (and secondary, in some cases) framework that must be used to get consistent look and feel across applications. macOS has Cocoa, Windows has WPF/MFC, Linux has GTK/Qt/etc, iOS has UIKit, Android has Material, and so on. These toolkits are incompatibly different in the most fundamental ways as the whole philosophy of a “graphical application” is different. They are written in different languages (Objective-C/Swift for macOS and iOS, C#/C++/CLR for Windows, C/C++ Linux, Java for Android) and have different event, drawing, runloop, and callback architectures. The task of writing a true native app for multiple platforms is really the same as writing several native apps with a common core.
There are efforts to make cross platform UI toolkits, such as Qt, wxWidgets, and SWT. These can produce an application that is quite reasonable in appearance yet doesn’t quite have the native look and feel. There’s usually differences in keyboard shortcuts, conventions such as where to place buttons/menus or what terminology to use, graphical artifacts, and lack of support for OS-specific features. Unfortunately, these frameworks also tend to be rather kitchen-sinky in design and may integrate poorly with other higher level libraries. For example, Qt provides threads and locks which may or may not map down to the same constructs as used by your cross platform core (using std::thread
, boost::thread
) – mix and match at your own peril.
Again, the point here is not that any of these frameworks are “doing it wrong”, but that the core issue of having multiple sort-of-but-not-really-compatible implementations of primitives such as threads, locks, processes, file access, strings, collections, etc. creates a tremendous – yet unwarranted – complexity that application developers must deal with.
The Modern “Native” GUI
There is one application environment where a single UI stack reigns: the web. At the root powered by HTML, Javascript, and CSS, but increasingly used through popular UI frameworks such as React, Bootstrap, Angular, etc., web applications can now deliver a rich user experience across desktops and mobile while retaining a “native” feel. Native not in the sense that they look like the OS widgets, but that the web has never had a platform-specific UI standard. This means that it is broadly acceptable for web applications to craft their own unique user interface while still largely adhering to some basic standards (buttons look like buttons, widget placement follows a few major trends, etc.). Some of the larger native applications (Adobe’s legendary Creative Suite comes to mind) do this as well, as it’s more important to their customers to have a consistent user experience across OSs than a perfectly native one.
Exposing a web rendering engine such as Blink or Webkit as a UI library in the libplatform
family would allow development of multi-platform applications with all the performance and capability benefits afforded to native apps: full access to threads, files, GPU, multiple windows, etc. This is an architecture that makes it easy to develop anything from a todo-list to a AAA game that can be distributed to anyone and run in a web browser or natively in the OS. A developer could have access to the raw DOM render tree and be able to modify it using DOM manipulation APIs, by injecting CSS or HTML, or by using canvas or “WebGL” (GLES) high-performance drawing functions.
You can think of this as a “web application” or a “native application” but in reality it is both. You could install it with package install myapp
and run it like a desktop app, or you could put it online and the user’s web browser could “host” the application’s render tree and provide user input. This may sound like a hypothetical “Electron Native”, which is true in so far as it provides the cross-platform UI capability (Electron) but does so using platform libraries (libplatform
) instead of a Javascript stack. This should substantially reduce application binary size and memory footprint with respect to Electron.
This model provides enormous advances in usability: an application can operate in a “lite” mode upon loading in a web browser, with an option to run the full version as a native app. In either case, the application binary is the same and the user gains ease of installation/updates of high-performance applications without sacrificing security. The browser can provide a sandboxed or restricted platform implementation (in the form of injecting an overriding library) that prevents an application running in the browser from harming a user’s system by exhausting resources or accessing the user’s data.
What About Mobile?
While most of these concepts would work on mobile (iOS, Android, etc.) in theory, the mobile app stores require review and approval of each application and applications can only be installed through the store (though Android can sideload applications manually). What may be more useful is for the libplatform
toolchain to package an application in a mobile-app compatible way, though there may be significant technical and policy barriers preventing this from working. There are multi-device frameworks such as Cordova that provide similar capabilities, however, so it should not be impossible, although the single binary promise would be broken.