Dokumentation
GPUI
Das GPUI-Backend zeichnet NSH-Apps mit GPUI (GPUI Fast), dem GPU-beschleunigten UI-Framework, auf dem GPUI Kit aufbaut, und zwar unter macOS, Windows und Linux gleichermaßen. Es ist das Backend zum Einbetten in GPUI- und GPUI-Kit-Apps: Ein AppView ist eine GPUI-View, sodass eine GPUI- oder GPUI-Kit-App NSH-Apps, ein eigenes Rust-Root oder geladenes TSX, an beliebiger Stelle in ihrem eigenen Elementbaum anzeigen kann. Eigenständige Apps laufen und werden mit dem jeweiligen Desktop-Backend paketiert: macOS (AppKit), Windows (Win32) und Linux (GTK). Der Core, der reaktive Graph, der Renderer, das Taffy-Layout und die nativen Module sind auf allen anderen Plattformen dieselben, sodass eine App überall läuft. os() (platform.os in TSX) ist das Betriebssystem, auf dem sie läuft (macOS, Windows oder Linux), und die desktop={{…}}-Props von TSX gelten.
Jedes Element und jedes Modul der Backends macOS (AppKit), Windows (Win32) und Linux (GTK) ist vorhanden. Wo GPUI etwas auf eigene Weise zeichnet, beschreibt Differences wie.
#Funktionsweise
- Ein Element pro Surface. Eine Surface ist ein GPUI-Element, das den Platz ausfüllt, den sein Parent ihm gibt, und den gesamten View-Baum mit den Primitiven von GPUI zeichnet: Quads, Schatten, Pfade, Glyphen und Bilder. NSH hält pro Node eine Retained View, wie unter Windows, und zeichnet nur neu, wenn ein Commit etwas verändert hat.
- Text wird auf dem Engine-Thread geshapet, mit dem Textsystem von GPUI (den eigenen Schriften der App), von NSH in Zeilen umgebrochen; der Main-Thread zeichnet nur noch die Glyphen.
- Ein Hop pro Commit. Die Engine läuft auf einem eigenen Thread; jeder Commit ist eine Closure, die an den Main-Thread von GPUI gesendet wird und die View benachrichtigt.
- Eingaben kommen aus den Maus-, Scroll-, Tasten- und Input-Method-Events von GPUI; Hit-Testing, Presses, das Responder-System, Scrollen und Textbearbeitung folgen derselben Logik wie im Windows-Backend.
#Voraussetzungen
-
macOS, Windows oder Linux, mit einer GPU, mit der GPUI zeichnen kann (Metal unter macOS, DirectX unter Windows, Vulkan unter Linux).
-
Unter macOS benötigt das Bauen Ihrer GPUI-App oder der Runtime aus einem Checkout die Metal Toolchain von Xcode (
xcodebuild -downloadComponent MetalToolchain), which compiles GPUI’s shaders ahead of time. On a Mac without it,nsh runbaut die Runtime mit dem GPUI-Featureruntime-shadersund weist darauf hin: Die Shader werden beim Start der App kompiliert (beim ersten Mal ein Sekundenbruchteil, danach von macOS gecacht). -
Unter Linux: ein Vulkan-Treiber, Wayland oder X11 mit
libxkbcommon, GTK 3 und WebKitGTK 4.1 (Web Views) sowie GStreamer 1.20 oder neuer mit den Base- und Good-Plug-ins (Videos). Web Views benötigen X11: Auf einem Wayland-Desktop starten Sie die App unter Xwayland (WAYLAND_DISPLAY=leer), sonst zeigen sie einen Platzhalter. Das Bauen der Runtime aus einem Checkout benötigt außerdem die Entwicklungspakete; unter Debian und 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
#Eine App ausführen
Eine Rust-App läuft innerhalb Ihrer GPUI-App: Betten Sie ihr Root mit AppView ein (Embedding in a GPUI app) und cargo run Sie die GPUI-App; Ihr Crate und die App sind ein gemeinsamer Cargo-Build. Eigenständig läuft eine Rust-App mit dem Backend des jeweiligen Betriebssystems (nsh run --target macos, windows oder linux).
Eine TSX-App läuft im GPUI-Development-Host:
nsh run App.tsx --target gpui
Er öffnet die App in einem GPUI-Fenster (standardmäßig 1200×800), so wie eine GPUI-App sie anzeigt, die sie einbettet, mit Hot Reload, Logs im Terminal und dem Error-Overlay wie bei den anderen Targets. Der Development-Host (nsh-gpui-host) läuft auf jedem Betriebssystem direkt am Ort, ohne App-Bundle drumherum. In einem Checkout baut nsh run ihn zuerst; die Distribution liefert ihn in runtime/gpui/ mit. Der Host führt außerdem ein gebautes Bundle eigenständig aus:
nsh build my-app --release -o build
dist/nsh/runtime/gpui/nsh-gpui-host --bundle build/bundle.nshc --window-size 390x844 # in a checkout: target/debug/nsh-gpui-host
--window-size ist die Größe des Fensters in Punkten (<width>x<height>).
#Paketierung
Eine GPUI-App liefert NSH innerhalb ihrer eigenen ausführbaren Datei aus: Sie bettet das Rust-Root oder das TSX-Bundle der App mit AppView ein (Embedding in a GPUI app) und wird als die GPUI-App gebaut und verteilt, die sie ist. Eine eigenständige NSH-App wird mit dem Target des jeweiligen Betriebssystems paketiert: --target macos, --target windows oder --target linux (Packaging).
Unter Linux linkt eine GPUI-App mit Web Views und Videos WebKitGTK 4.1 und GStreamer dynamisch, sodass die Rechner, auf denen sie läuft, diese installiert haben müssen, dazu GTK 3 sowie die Base- und Good-Plug-ins von GStreamer (Debian / Ubuntu: libwebkit2gtk-4.1-0, libgstreamer1.0-0, libgstreamer-plugins-base1.0-0, gstreamer1.0-plugins-good), außerdem einen Vulkan-Treiber und libxkbcommon.
#In eine GPUI-App einbetten
Das Crate nsh-platform-gpui (aus dem NSH-Repository) gibt einer Rust-GPUI-App AppView: eine NSH-Engine als GPUI-View (Entity<AppView>), die den Platz ausfüllt, den ihr Parent-Element ihr gibt. Jedes AppView ist unabhängig; mehrere können gleichzeitig laufen. Es führt ein Rust-Root aus, die Root-Komponente Ihres Crates:
use gpui::{AppContext, WindowOptions};
use nsh_platform_gpui::{set_ui_sets, App, AppOptions, AppView, Root};
// Once, before the first view: the UI sets the apps import (NSH's and your crate's).
set_ui_sets(vec![nsh_base::registry(), my_app::registry()]);
gpui_platform::application().run(|cx| {
cx.open_window(WindowOptions::default(), |window, cx| {
cx.new(|cx| {
let view = AppView::new(AppOptions::default(), window, cx);
view.load(App::Rust(Root::of::<my_app::App>()));
view
})
})
.unwrap();
});
Root::tokens gibt dem Root sein Theme (nsh_base::Theme::from_json(json)?.into_tokens(), Theme wird hier re-exportiert) und Root::app_id die ID, unter der seine Daten gespeichert werden. Ein TSX-Bundle, das nsh build geschrieben hat, lässt sich in dieselbe View laden:
use nsh_platform_gpui::{App, Bundle};
let code = std::fs::read_to_string("app/bundle.js").expect("bundle");
view.load(App::Js(Bundle::source(code)));
Setzen Sie die Entity wie jede andere View in Ihren Elementbaum (ein GPUI-Kit-Root, ein Panel, ein Dock). AppView::load ersetzt, was zuvor lief; stop (oder das Verwerfen der View) beendet die App. AppOptions::asset_base legt fest, wo asset:/-URIs aufgelöst werden (AssetBase::Directory oder AssetBase::Http). Bytecode-Bundles (bundle.nshc) werden mit unsafe { Bundle::bytecode(bytes) } geladen (oder Bundle::read_trusted(path), ebenfalls unsafe): QuickJS-Bytecode ist vertrauenswürdige Eingabe, laden Sie ihn daher nur aus den eigenen Dateien Ihrer App.
open_app_window(source, options, cx)öffnet in einer laufenden GPUI-App ein Fenster mit einemAppView.run_app(source, options)startet GPUI und führt eine App in einem Fenster aus, bis es geschlossen wird (das, was der Development-Host tut).AppView::register_component(name, factory)registriert vor dem Laden eine eigene native Komponente von Ihnen (Native components).AppOptions::cache(oderAppView::set_cache, für die als Nächstes geladenen Apps) legt fest, wo die Apps ihre Caches ablegen, die Bilder, die sie herunterladen, und wie viel jede davon behält:CacheOptions { location, images },CacheLocation::System(the default),Dir(path)orOff,CachePolicy::Default(256 MiB),MaxSize(ByteSize::mib(n))oderOff.AppView::host()ist der Kanal der View zu den Apps, die sie ausführt (host), einHostChannel, den jeder Thread nutzen darf:set_params(map)vor dem Laden gibt der App ihre Startparameter (host.params),send(name, data)ein Event (host.on), undhandle(name, |call| …)beantwortet ihrehost.call(name, data)und hört ihrehost.post(call.resolve(json),call.reject(message), sofort oder später von einem beliebigen Thread). Handler laufen auf dem Engine-Thread: Geben Sie den Aufruf an Ihre GPUI-Entities weiter (ein Kanal, den eincx.spawn-Task liest) und antworten Sie von dort. DieAppViews für macOS, Windows und Linux haben dasselbehost(). Ihrhandle_open_url(|request| …)sieht die URLs, die die App öffnet (openUrl), bevor das System sie sieht:request.handled(), wenn Ihre App die URL selbst weitergeleitet hat,request.decline()(oder das Verwerfen der Anfrage), damit das System sie mit demopen_urlvon GPUI öffnet.AppOptions::limits(oderAppView::set_limits, für die als Nächstes geladenen Apps) legt die VM-Limits fest: den JS-Heap, den Stack, die Zeitbudgets und das Node-Kontingent (Sandbox and capabilities). Eingebettete Apps erhaltenLimits::default()(ein 256-MB-Heap); der Development-Host führt Apps eigenständig aus, mitLimits::standalone().AppOptions::transparentmacht die View durchsichtig: Sie zeichnet keinen Fensterhintergrund, undopen_app_window/run_appöffnen ein transparentes Fenster, sodass das, was die App unbemalt lässt, zeigt, was hinter dem Fenster liegt (Desktop-Widgets, Overlays). Standardmäßig false.AppView::connect_dev_server(url, cx)(Featuredev) lädt die App ausnsh devund lädt sie beim Bearbeiten neu, mit dem Error-Overlay über der View;AppView::on_errorhört weiterhin die Fehler der App.AppView::on_app_error(|error| …)hört die Fehler der App:error.kind(die App konnte nicht geladen werden, eine nicht abgefangene Exception, die VM wurde gestoppt),error.is_fatal(), wenn kein JS der App mehr läuft,error.message,error.stackunderror.package(die ID und der Build des signierten Pakets, aus dem sie stammt).AppView::on_errorerhält dieselben Fehler als Text.AppView::set_locale(locale, cx)undAppView::set_font_scale(scale, cx)geben der App die Sprache und Textgröße Ihrer App anstelle der des Systems (Nonefolgt wieder dem System):locale(),Intl,fontScale()und Text folgen sofort (Device and environment).AppView::run_package(&package)führt ein signiertes Paket ausverify_packageoderload_packageaus;accept_packagehält fest, dass Ihre App es für fehlerfrei befunden hat.set_ui_sets(sets)legt die UI-Sets fest, die jede Engine bereitstellt: die von NSH (nsh_base::registry()) und die Ihrer Crates (my_app::registry(), das#[native_module]erzeugt; Building with Rust), und gibt die eigenen Sets Ihrer App: Eine App erreicht die Funktionen und Klassen eines Sets per Name mitnativeModuleund wird mit dem Standard-nsh buildgebaut. Dasexamples/embed-gpuides Repositorys betreibt auf diese Weise einen Trading Desk im Host, den zwei eingebettete Order-Tickets und die eigenen Widgets des Hosts gemeinsam nutzen.
#Unterschiede
Was anders zeichnet oder sich anders verhält als bei den nativen Desktop-Backends:
- Transforms: Gedrehte, geneigte und projizierte Views drehen ihre Hintergründe, Rahmen und Schatten mit, aber Text bleibt aufrecht, und Bilder, Videos und Canvases werden an ihren Bounds gezeichnet.
- Opacity wird auf jede Farbe angewendet statt auf eine Gruppe, sodass sich überlappende Kinder einer durchscheinenden View gegenseitig durchscheinen lassen; die Opacity eines Bildes (ein Einblenden, eine durchscheinende View) ändert sich in Sechzehnteln.
- Abgerundete Clips (
overflow: "hidden"mit Radius) runden die Hintergründe und Bilder der Kinder ab, aber Text, Rahmen, Videos und native Komponenten werden vom Rechteck abgeschnitten. - Nicht gezeichnet:
filter: blur(),backdropFilterundmixBlendMode; Schatten scheinen durch durchscheinende Füllungen hindurch;borderCurve: "continuous"zeichnet kreisrunde Ecken. - Canvas-Text wird angenähert: aufrecht bei Rotationen, bei
maxWidthkleiner statt schmaler gemacht, Verläufe zeichnen ihn einfarbig,strokeText-Füllungen, seine Schatten sind nicht weichgezeichnet, und Kompositionsoperationen sowieclearRectwerden darauf nicht angewendet. - Scroll Views zoomen nicht (
zoomTound die Zoom-Props werden ignoriert). - Video wird unter Linux abgespielt (GStreamer); unter macOS und Windows zeigt ein Video einen Platzhalter und meldet
onError. - Web Views sind native Views oberhalb von allem, was NSH an ihrer Stelle zeichnet. Unter Linux benötigen sie X11 (siehe Requirements); wo Seiten unter Xwayland schwarz bleiben („Failed to create GBM buffer“ im Log), starten Sie die App mit
WEBKIT_DISABLE_DMABUF_RENDERER=1. Unter macOS und Windows schneiden clippende Vorfahren sie nicht ab, und fehlgeschlagene Ladevorgänge melden keinonError.baseUrl,scrollableundallowsBackForwardNavigationGestureswerden nicht unterstützt. - Accessibility läuft über AccessKit: Screenreader können überall Elemente aktivieren, fokussieren, Werte setzen und Elemente in den sichtbaren Bereich scrollen; benutzerdefinierte Aktionen und die Kontextmenü-Aktion werden noch nicht weitergegeben.
Zum Vergleich die jeweilige Liste jedes nativen Backends: macOS, Windows, Linux.
#Siehe auch
- Building with Rust: das Crate, dessen Root eine GPUI-App einbettet
- CLI:
nsh run --target gpui - Packaging: eigenständige Apps, mit dem Target des jeweiligen Betriebssystems
- Platform overview