本頁由機器翻譯,可能有錯誤。 閱讀英文原文

文件

GPUI

GPUI 後端以 GPUI(GPUI Fast)繪製 NSH 應用程式。GPUI 是 GPU 加速的 UI 框架,亦是 GPUI Kit 的基礎,在 macOS、Windows 和 Linux 上均可使用。它是嵌入 GPUI 和 GPUI Kit 應用程式時使用的後端:AppView 是一個 GPUI view,因此 GPUI 或 GPUI Kit 應用程式可在自己的元素樹中任何位置顯示 NSH 應用程式,無論是自帶的 Rust 根元件,還是它載入的 TSX。獨立應用程式則以各桌面平台自己的後端執行和打包:macOS(AppKit)、Windows(Win32)和 Linux(GTK)。核心、響應式圖、渲染器、Taffy 佈局和原生模組與其他平台完全相同,因此一個應用程式可以在所有平台上執行。os()(TSX 中為 platform.os)是應用程式所運行的作業系統(macOS、Windows 或 Linux),TSX 的 desktop={{…}} props 同樣適用。

macOS(AppKit)、Windows(Win32)和 Linux(GTK)後端的所有元素和模組,在此後端中一應俱全;GPUI 以自己方式繪製的部分,請參閱差異。

#運作方式

  • 每個 surface 一個元素。 一個 surface 是一個 GPUI 元素,會填滿其父元素提供的空間,並以 GPUI 的基本圖元(quad、陰影、路徑、字形和圖片)繪製整個視圖樹。與 Windows 一樣,NSH 為每個節點保留一個 retained view,只在 commit 改動了內容時才重繪。
  • 文字在引擎執行緒上排版,使用 GPUI 的文字系統(應用程式自己的字型),由 NSH 斷行;主執行緒只負責繪製字形。
  • 每次 commit 只跳轉一次。 引擎在自己的執行緒上運行;每次 commit 是一個傳送到 GPUI 主執行緒的 closure,由它通知視圖。
  • 輸入來自 GPUI 的滑鼠、捲動、按鍵和輸入法事件;命中測試、按壓、responder 系統、捲動和文字編輯的邏輯,與 Windows 後端相同。

#系統要求

  • macOS、Windows 或 Linux,並配備 GPUI 可使用的 GPU(macOS 用 Metal,Windows 用 DirectX,Linux 用 Vulkan)。

  • 在 macOS 上,從 checkout 建置 GPUI 應用程式或 runtime,需要 Xcode 的 Metal Toolchain(xcodebuild -downloadComponent MetalToolchain), which compiles GPUI’s shaders ahead of time. On a Mac without it, nsh run 會以 GPUI 的 runtime-shaders feature 建置 runtime 並加以說明):著色器會在應用程式啟動時編譯(首次約需一瞬間,之後由 macOS 快取)。

  • 在 Linux 上:需要 Vulkan 驅動程式、支援 libxkbcommon 的 Wayland 或 X11、GTK 3 和 WebKitGTK 4.1(web view),以及 GStreamer 1.20 或更新版本連同其 base 和 good 外掛程式(影片)。Web view 需要 X11:在 Wayland 桌面上,請在 Xwayland 下執行應用程式(WAYLAND_DISPLAY= 為空),否則 web view 會顯示佔位符。從 checkout 建置 runtime 亦需要開發套件;在 Debian 和 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

#執行應用程式

Rust 應用程式在你的 GPUI 應用程式內運行:以 AppView 嵌入其根元件(嵌入 GPUI 應用程式),再 cargo run GPUI 應用程式;你的 crate 與應用程式屬於同一個 Cargo 建置。單獨執行時,Rust 應用程式使用此作業系統自己的後端(nsh run --target macos、windows 或 linux)。

TSX 應用程式在 GPUI 開發主機中運行:

nsh run App.tsx --target gpui

它會在 GPUI 視窗中開啟應用程式(預設 1200×800),效果與嵌入它的 GPUI 應用程式所顯示的相同,並提供熱重載、終端機日誌和錯誤覆蓋層,與其他目標一樣。開發主機(nsh-gpui-host)在每個作業系統上都直接原地運行,不附帶 app bundle。在 checkout 中,nsh run 會先建置它;發行版本則把它放在 runtime/gpui/ 中。主機亦可單獨執行已建置的 bundle:

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 是視窗的大小,以 point 為單位(<width>x<height>)。

#打包

GPUI 應用程式把 NSH 放進自己的可執行檔:以 AppView 嵌入應用程式的 Rust 根元件或 TSX bundle(嵌入 GPUI 應用程式),並按 GPUI 應用程式本身的方式建置和發佈。獨立的 NSH 應用程式則使用此作業系統自己的目標打包:--target macos、--target windows或 --target linux(打包)。

在 Linux 上,含有 web view 和影片的 GPUI 應用程式會動態連結 WebKitGTK 4.1 和 GStreamer,因此運行它的機器必須安裝這些套件,連同 GTK 3 以及 GStreamer 的 base 和 good 外掛程式(Debian / Ubuntu:libwebkit2gtk-4.1-0、libgstreamer1.0-0、libgstreamer-plugins-base1.0-0、gstreamer1.0-plugins-good),另外還需要 Vulkan 驅動程式和 libxkbcommon。

#嵌入 GPUI 應用程式

nsh-platform-gpui crate(來自 NSH 儲存庫)為 Rust GPUI 應用程式提供 AppView:一個 NSH 引擎,以 GPUI view(Entity<AppView>)的形式存在,會填滿其父元素提供的空間。每個 AppView 都是獨立的,可同時運行多個。它運行 Rust 根元件,即你的 crate 的根 component:

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 為根元件提供其主題(nsh_base::Theme::from_json(json)?.into_tokens(),此處重新匯出 Theme),Root::app_id 則提供保存其資料所用的 id。由 nsh build 編寫的 TSX bundle 可載入同一個視圖:

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)));

把這個 entity 放進你的元素樹,與其他 view 一樣(GPUI Kit 的 Root、面板或 dock)。AppView::load 會取代正在運行的內容;stop(或丟棄該 view)會結束應用程式。AppOptions::asset_base 指明 asset:/ URI 的解析位置(AssetBase::Directory 或 AssetBase::Http)。位元組碼 bundle(bundle.nshc)以 unsafe { Bundle::bytecode(bytes) }(或 Bundle::read_trusted(path),同樣是 unsafe)載入:QuickJS 位元組碼屬於受信任的輸入,因此只應從你自己應用程式的檔案載入。

  • open_app_window(source, options, cx) 在運行中的 GPUI 應用程式裡開啟一個視窗,內含一個 AppView。
  • run_app(source, options) 啟動 GPUI,並在視窗中運行一個應用程式直至視窗關閉(即開發主機的做法)。
  • AppView::register_component(name, factory) 在載入前註冊你自己的原生元件(原生元件)。
  • AppOptions::cache(或 AppView::set_cache,用於接下來載入的應用程式)設定應用程式存放快取的位置、它們下載的圖片,以及每個應用程式保留的容量:CacheOptions { location, images }, CacheLocation::System (the default), Dir(path) or Off, CachePolicy::Default(256 MiB)、MaxSize(ByteSize::mib(n)) 或 Off。
  • AppView::host() 是 view 與其運行的應用程式之間的通道(host),是任何執行緒都可使用的 HostChannel:在載入前呼叫 set_params(map) 可為應用程式提供啟動參數(host.params),send(name, data) 發送事件(host.on),handle(name, |call| …) 則回應其 host.call(name, data) 並接收其 host.post(call.resolve(json)、call.reject(message),可即時或稍後從任何執行緒回應)。處理函式在引擎執行緒上運行:請把呼叫交給你的 GPUI entity(例如由 cx.spawn 任務讀取的 channel),並在那裡回應。macOS、Windows 和 Linux 的 AppView 具有相同的 host()。它的 handle_open_url(|request| …) 會在系統之前看到應用程式開啟的 URL(openUrl):若你的應用程式已自行處理該 URL,則回傳 request.handled();回傳 request.decline()(或丟棄該請求)則交由系統,以 GPUI 的 open_url 開啟。
  • AppOptions::limits(或 AppView::set_limits,用於接下來載入的應用程式)設定 VM 限制:JS heap、stack、時間預算和節點配額(沙盒與權能)。嵌入的應用程式採用 Limits::default()(256 MB heap);開發主機獨立運行應用程式,使用 Limits::standalone()。
  • AppOptions::transparent 令 view 變為透明:它不繪製視窗背景,而 open_app_window / run_app 會開啟透明視窗,因此應用程式未繪製的部分會顯示視窗後面的內容(桌面小工具、覆蓋層)。預設為 false。
  • AppView::connect_dev_server(url, cx)(feature dev)從 nsh dev 載入應用程式,並在你編輯時重新載入,錯誤覆蓋層會顯示在 view 上方;AppView::on_error 會繼續接收應用程式的錯誤。
  • AppView::on_app_error(|error| …) 接收應用程式的錯誤:error.kind(應用程式載入失敗、未捕獲的例外、VM 已停止)、error.is_fatal()(應用程式的 JS 不會再運行時)、error.message、error.stack 和 error.package(它所來自的已簽署套件的 id 和 build)。AppView::on_error 會以文字形式取得相同的錯誤。
  • AppView::set_locale(locale, cx) 和 AppView::set_font_scale(scale, cx) 會把你應用程式的語言和文字大小提供給應用程式,取代系統的設定(None 則恢復跟隨系統):locale()、Intl、fontScale() 和文字會即時跟隨變化(裝置與環境)。
  • AppView::run_package(&package) 從 verify_package 或 load_package 運行已簽署套件;待你的應用程式判斷它運作正常後,accept_package 會記錄這一點。
  • set_ui_sets(sets) 設定每個引擎提供的 UI 集:NSH 的(nsh_base::registry())和你的 crate 的(my_app::registry(),由 #[native_module] 產生;以 Rust 建置),並提供你應用程式自己的集:應用程式可用 nativeModule按名稱存取其中的函式和類別,並以標準的 nsh build 建置。儲存庫的 examples/embed-gpui 就以這種方式在主機中運行一個交易台,由兩個嵌入的下單票據與主機自己的小工具共用。

#差異

與原生桌面後端相比,繪製或行為不同之處:

  • 變換:旋轉、傾斜和投影的視圖會轉動其背景、邊框和陰影,但文字保持直立,圖片、影片和 canvas 按其邊界繪製。
  • 不透明度逐個顏色套用,而非整組套用,因此半透明視圖中互相重疊的子元素會互相透出;圖片的不透明度(淡入、半透明視圖)以十六分之一為單位變化。
  • 圓角裁剪(帶半徑的 overflow: "hidden")會裁圓子元素的背景和圖片,但文字、邊框、影片和原生元件仍按矩形裁剪。
  • 不繪製:filter: blur()、backdropFilter 和 mixBlendMode;陰影會透過半透明填色顯示;borderCurve: "continuous" 繪製圓形圓角。
  • Canvas 文字屬近似處理:旋轉時保持直立,對 maxWidth 是縮小而非收窄,漸層只會以單一顏色繪製文字,strokeText 填色,其陰影不會模糊,合成運算和 clearRect 不適用於它。
  • 捲動視圖不支援縮放(zoomTo 和縮放 props 會被忽略)。
  • 影片在 Linux 上可播放(GStreamer);在 macOS 和 Windows 上,影片會顯示佔位符並回報 onError。
  • Web view 是位於 NSH 所繪內容之上、在其位置顯示的原生 view。在 Linux 上它們需要 X11(見系統要求);若在 Xwayland 下頁面一片漆黑(日誌中出現 “Failed to create GBM buffer”),請以 WEBKIT_DISABLE_DMABUF_RENDERER=1 運行應用程式。在 macOS 和 Windows 上,裁剪用的祖先元素不會裁切它們,載入失敗亦不會回報 onError。baseUrl、scrollable 和 allowsBackForwardNavigationGestures 不受支援。
  • 無障礙功能透過 AccessKit 實現:螢幕閱讀器在所有平台上都可啟用、聚焦、設定值,以及把元素捲動至可見範圍;自訂動作和 context-menu 動作暫時尚未傳遞。

如需比較,各原生後端自己的清單:macOS、Windows、Linux。

#另請參閱