Docs

GPUI

The GPUI backend draws NSH apps with GPUI (GPUI Fast), the GPU-accelerated UI framework that GPUI Kit is built on, on macOS, Windows and Linux alike. Its main use is embedding: an AppView is a GPUI view, so a GPUI or GPUI Kit app shows NSH modules anywhere in its own element tree. The core, the reactive graph, the renderer, Taffy layout and the nsh/ui/mobile components are the same as on every other platform, so one bundle runs everywhere. Platform.OS is the OS it runs on ("macos", "windows" or "linux") and the desktop={{…}} props apply.

Every element and module of the macOS (AppKit), Windows (Win32) and Linux (GTK) backends is there; where GPUI draws something its own way, Differences says how.

#How it works

  • One element per surface. A surface is one GPUI element that fills the space its parent gives it and paints the whole view tree with GPUI’s primitives: quads, shadows, paths, glyphs and images. NSH keeps one retained view per node, as on Windows, and redraws only when a commit changed something.
  • Text is shaped on the engine thread with GPUI’s text system (the app’s own fonts), broken into lines by NSH; the main thread only paints the glyphs.
  • One hop per commit. The engine runs on its own thread; each commit is one closure sent to GPUI’s main thread, which notifies the view.
  • Input comes from GPUI’s mouse, scroll, key and input method events; hit testing, presses, the responder system, scrolling and text editing are the same logic as the Windows backend’s.

#Requirements

  • macOS, Windows or Linux, with a GPU GPUI can draw with (Metal on macOS, DirectX on Windows, Vulkan on Linux).

  • On Linux: a Vulkan driver, Wayland or X11 with libxkbcommon, GTK 3 and WebKitGTK 4.1 (web views) and GStreamer 1.20 or later with its base and good plug-ins (videos). Web views need X11: on a Wayland desktop run the app under Xwayland (WAYLAND_DISPLAY= empty), else they show a placeholder. Building the runtime from a checkout also needs the development packages; on Debian and Ubuntu:

    sudo apt-get install clang libclang-dev pkg-config libfontconfig-dev libfreetype-dev libwayland-dev libx11-xcb-dev \
      libxcb1-dev libxkbcommon-dev libxkbcommon-x11-dev libzstd-dev libvulkan1 libgtk-3-dev libwebkit2gtk-4.1-dev \
      libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-good

#Running an app

nsh run my-app --target gpui

Opens the app in a GPUI window (1200×800 by default), with hot reload, logs in the terminal and the error overlay as on the other targets. The development host (nsh-gpui-host) runs in place on every OS, with no app bundle around it. In a checkout, nsh run builds it first; the distribution ships it in runtime/gpui/. The host also runs a built bundle on its own:

nsh build my-app --release -o build
dist/nsh/runtime/gpui/nsh-gpui-host --bundle build/bundle.nshc --size 390x844   # in a checkout: target/debug/nsh-gpui-host

--size is the window’s size in points (<width>x<height>).

#Packaging

nsh package my-app --target gpui [--run]

The package is laid out as the native backend’s of the machine it is made on, with GPUI’s release runner (nsh-gpui-app) as the executable:

Packaged onOutput
macOSan ad hoc signed <Name>.app, as --target macos makes
Windowsa directory <Name>\ with <Name>.exe and nsh\, and <Name>.zip, as --target windows makes
Linuxa directory <Name>/ with the executable, nsh/ and a .desktop file, and <Name>.tar.gz, as --target linux makes

As with the native backends, the runner is built for the machine it is built on: package a Mac app on a Mac, a Windows app on Windows and a Linux app on Linux. --run launches the app after packaging it.

A Linux package links WebKitGTK 4.1 (web views) and GStreamer (videos) dynamically, so the machines it runs on need them installed, with GTK 3 and GStreamer’s base and good plug-ins (Debian / Ubuntu: libwebkit2gtk-4.1-0, libgstreamer1.0-0, libgstreamer-plugins-base1.0-0, gstreamer1.0-plugins-good), besides a Vulkan driver and libxkbcommon.

#Embedding in a GPUI app

The nsh-platform-gpui crate (from the NSH repository) gives a Rust GPUI app AppView: one NSH engine as a GPUI view (Entity<AppView>), which fills the space its parent element gives it. Each AppView is independent; several can run at once.

use gpui::{AppContext, Application, WindowOptions};
use nsh_platform_gpui::{App, AppOptions, AppView, Bundle};

gpui_platform::application().run(|cx| {
    cx.open_window(WindowOptions::default(), |window, cx| {
        cx.new(|cx| {
            let view = AppView::new(AppOptions::default(), window, cx);
            let code = std::fs::read_to_string("app/bundle.js").expect("bundle");
            view.load(App::Js(Bundle::source(code)));
            view
        })
    })
    .unwrap();
});

Put the entity in your element tree like any other view (a GPUI Kit Root, a panel, a dock). AppView::load replaces whatever ran; stop (or dropping the view) ends the app. AppOptions::asset_base tells it where asset:/ URIs resolve (AssetBase::Directory or AssetBase::Http). Bytecode bundles (bundle.nshc) load with unsafe { Bundle::bytecode(bytes) } (or Bundle::read_trusted(path), unsafe too): QuickJS bytecode is trusted input, so only load it from your own app’s files.

  • open_app_window(source, options, cx) opens a window with one AppView in a running GPUI app.
  • run_app(source, options) starts GPUI and runs one app in a window until it closes (what the release runner does).
  • AppView::register_component(name, factory) registers a native component of yours before loading (Native components).
  • set_ui_sets(sets) replaces the default nsh/ui/mobile set, for apps with Rust components of their own.

#Differences

What draws or behaves differently from the native desktop backends:

  • Transforms: rotated, skewed and projected views turn their backgrounds, borders and shadows, but text stays upright, and images, videos and canvases draw at their bounds.
  • Opacity applies to each color rather than to a group, so overlapping children of a translucent view show through each other; an image’s opacity (a fade-in, a translucent view) changes in sixteenths.
  • Rounded clips (overflow: "hidden" with a radius) round the backgrounds and images of the children, but text, borders, videos and native components are cut by the rectangle.
  • Not drawn: filter: blur(), backdropFilter and mixBlendMode; shadows show through translucent fills; borderCurve: "continuous" draws circular corners.
  • Canvas text is approximated: upright under rotations, made smaller rather than narrower for maxWidth, gradients paint it in one color, strokeText fills, its shadows are not blurred, and composite operations and clearRect do not apply to it.
  • Scroll views do not zoom (zoomTo and the zoom props are ignored).
  • Video plays on Linux (GStreamer); on macOS and Windows a video shows a placeholder and reports onError.
  • Web views are native views above everything NSH draws at their place. On Linux they need X11 (see Requirements); where pages stay black under Xwayland (“Failed to create GBM buffer” in the log), run the app with WEBKIT_DISABLE_DMABUF_RENDERER=1. On macOS and Windows clipping ancestors do not cut them and failed loads report no onError. source.baseUrl, scrollEnabled and allowsBackForwardNavigationGestures are not supported.
  • Accessibility goes through AccessKit: screen readers can activate, focus, set values and scroll elements into view everywhere; custom actions and the context-menu action are not passed on yet.

For comparison, each native backend’s own list: macOS, Windows, Linux.

#See also