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

文件

Windows

NSH 應用程式也能在 Windows 上執行。Windows 後端使用 Direct2D 在 Win32 視窗中繪製應用程式,並以 DirectWrite 排版文字;核心、響應式圖(reactive graph)、渲染器、Taffy 版面配置以及原生模組都與 iOS 和 Android 相同,因此同一個應用程式可以在所有平台上執行。在 Windows 上,os() 為 Os::Windows(platform.os 在 TSX 中為 "windows"),且 TSX 的 desktop={{…}} 屬性同樣適用。視窗標題、應用程式自訂的標題列、選單列與右鍵選單,在 Rust 中來自 nsh_core::element::desktop(桌面版標題列),在 TSX 中來自 nsh/desktop。

#系統需求

  • Windows 10 1809 或更新版本,或 Windows 11。
  • 網頁檢視需要 WebView2 Runtime。Windows 11 以及 Windows 10 上的 Microsoft Edge 皆已內建;若沒有安裝,WebView 會顯示預留位置。
  • 應用程式播放的影片格式需要對應的 Media Foundation 編解碼器(影片透過 Windows 自身的媒體堆疊播放)。
  • 若要建置 Rust 應用程式的 runner:需要安裝含 C++ 工作負載的 Visual Studio Build Tools 以及 Windows SDK。若要建置 NSH 的 Windows 執行階段(從原始碼 checkout 建置,或在 Windows 上執行 cargo xtask dist),還需要 LLVM(供 QuickJS 的繫結使用)。

#執行應用程式

在 Windows 上,nsh run 會在視窗中開啟應用程式。Rust 應用程式在其目錄下執行:

nsh run                        # --target windows is the default on Windows

nsh 會以你的 crate 建置除錯版 runner 並開啟應用程式,其記錄(log::info! 等)會輸出在終端機中;當 crate 的 Rust 程式碼有變更時,會重新建置 runner 並重新啟動應用程式。cargo run 會從 crate 的 src/main.rs(由 nsh init --template rust 寫入)啟動相同的根元件:run_app,搭配 AppSource::Rust。

TSX 應用程式需指定其進入點,預設開啟 1200×800 的視窗,並提供熱重載、終端機記錄與錯誤覆蓋層,與其他目標平台相同:

nsh run App.tsx

在 checkout 中,nsh run 會先建置開發用 host(nsh-windows-host.exe);在 Windows 上製作的發行版會將它放在 runtime\windows\ 中。按下 Ctrl-C 會關閉視窗,而關閉視窗則會結束 nsh run;無論以何種方式結束,host 都不會比 nsh 活得更久。host 也可以單獨執行已建置的 bundle:

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

--window-size 為視窗大小,單位為點(<width>x<height>;僅限 TSX 應用程式)。

#效能監控

--fps(nsh run App.tsx --target windows --fps、nsh test --target windows --fps、host 的 --fps,或在 Rust 應用程式的 run_app 中使用 AppOptions { fps: true, .. })會每秒記錄一次效能監控的讀數(目標 nsh::perf):包括顯示的幀數與顯示器更新率之比、主執行緒每幀的耗時及其各階段(tick、drain、paint、present、canvas)、引擎的提交次數、CPU 與記憶體,格式與其他桌面平台的記錄相同(毫秒以 mean/p50/p95/max 表示)。nsh test --fps -v 會顯示這些讀數,examples/gallery/tools/perf.py 則會讀取它們(效能報告)。請在 release 版本上進行量測;監控關閉時不會產生任何成本。

#打包

nsh package --target windows [--run]           # a Rust app, from its directory
nsh package App.tsx --target windows [--run]   # a TSX app

會產生一個目錄,以及內容相同的封存檔:

.nsh\out\<Name>\
  <Name>.exe             the executable, with the app's icon: a Rust app's own runner, built with its crate; nsh's release runner for TSX
  nsh\bundle.nshc         a TSX app, compiled
  nsh\assets\…           the assets it imports (a Rust crate's `assets!()` too)
  nsh\app.json           the window title, the app id, the version and the appearance
.nsh\out\<Name>.zip

可執行檔會在自身旁邊尋找 nsh\;請將整個目錄複製(或解壓縮 .zip)到應用程式要存放的位置:不需要任何安裝。來自 nsh.json 的圖示會嵌入可執行檔中(檔案總管與工作列都會顯示它),不需要任何額外工具;若在其他系統上打包,則會保留預設圖示。應用程式 id 用於在工作列中將應用程式的視窗歸為一組。--run 會在打包後啟動應用程式。可執行檔未經簽署:若要發行,請以你自己的憑證簽署(signtool)。

#嵌入 Win32 應用程式

nsh-platform-windows crate(來自 NSH 儲存庫)為 Rust 的 Win32 應用程式提供 AppView:在你自己的視窗之子視窗中執行一個 NSH 引擎。每個 AppView 都是獨立的,可以同時執行多個。它執行的是 Rust 根元件,也就是你 crate 的根元件:

use std::rc::Rc;
use nsh_platform_windows::{
    windows::{self, Win32::{Foundation::HWND, System::Ole::OleInitialize, UI::WindowsAndMessaging::MoveWindow}},
    set_ui_sets, AppOptions, AppView, Root,
};

/// Called on the thread that runs your message loop, with your window.
fn embed(parent: HWND) -> windows::core::Result<Rc<AppView>> {
    // Once per thread: COM and OLE (the clipboard, image decoding).
    unsafe { OleInitialize(None)? };
    // 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()]);
    let view = AppView::new(
        Some(parent),
        AppOptions {
            app_id: "com.example.host".into(),
            ..AppOptions::default()
        },
    )?;
    // register native components here, before loading
    view.load(Root::of::<my_app::App>().into());
    Ok(view)
}

// On WM_SIZE of `parent`:
// unsafe { MoveWindow(view.hwnd(), 0, 0, width, height, true) };

由 nsh build 產生的 TSX bundle 可以載入同一個檢視中,並將 asset_base 設為其 assets 目錄:

let code = std::fs::read_to_string("app/bundle.js").expect("bundle");
view.load(Bundle::source(code).into());

此檢視是一個子視窗:由你的訊息迴圈驅動它,當父視窗調整大小時,請以其 hwnd() 上的 MoveWindow 來擺放它。請在其所在視窗的執行緒上建立它:其生命週期跟隨該視窗的最上層視窗,也就是建立它時所在的那個。AppView::load 會取代正在執行的內容;stop(或銷毀該視窗)會結束應用程式。asset_base 用來指定 asset:/ URI 的解析位置(AssetBase::Directory 或 AssetBase::Http)。位元組碼 bundle(bundle.nshc)可用 unsafe { Bundle::bytecode(bytes) }(或同樣屬於 unsafe 的 Bundle::read_trusted(path))載入:QuickJS 位元組碼屬於受信任的輸入,因此只能從你自己應用程式的檔案載入。獨立視窗為 nsh_platform_windows::run_app(source, options),可搭配 AppSource::Rust(root) 或 AppSource::Bundle(bundle)。Root::tokens 會為根元件指定其主題,Root::app_id 則指定用來保存其資料的 id。

AppOptions::cache(或用於之後載入之應用程式的 AppView::set_cache)會設定應用程式存放快取的位置、其下載的圖片,以及各自保留的容量(CacheOptions:預設為系統的快取目錄與 256 MiB 的圖片)。

AppView::set_locale(Some(locale)) 與 AppView::set_font_scale(Some(scale)) 會讓應用程式使用你的應用程式所設定的語言與文字大小,而非系統的設定(None 會再次跟隨系統):locale()、Intl、fontScale() 與文字會立即跟著更新(裝置與環境)。

AppView::set_theme_overrides(Some(json)) 會將你的應用程式品牌套用在應用程式的主題之上:這是一個主題檔,格式與 nsh.json 的 "theme" 相同,其中的 token 在淺色與深色模式下會優先於應用程式中同名的 token,而應用程式的其他 token 則維持不變;諸如 bg-primary 的類別會立即重新套用樣式,之後載入的應用程式也會以它作為起點(None 會把應用程式自己的主題還給它;無法解析的檔案會視為錯誤,且不會變更任何內容)。

原生元件以 AppView::register_component 註冊;請參閱原生元件。set_ui_sets(sets) 會設定每個引擎所提供的 UI 集合:NSH 的(nsh_base::registry())以及你的 crate 的(my_app::registry(),由 #[native_module] 產生),因此檢視所載入的 TSX 也能匯入你的 Rust 元件與模組。

#Windows 上的差異

環境與桌面平台一致:安全區域與鍵盤高度為 0,idiom() 為桌面(platform.idiom 為 "desktop"),os_version()(platform.version)為 "10.0"(在 Windows 11 上也是如此:Windows 沿用了相同的版本號)。深色模式會即時跟隨「設定 > 個人化 > 色彩」,fontScale() 跟隨「文字大小」設定,減少動態效果跟隨動畫效果設定,地區設定則跟隨使用者的地區格式。請參閱裝置與環境。

cursor 樣式會在滑鼠停留於檢視上時跟著更新。當一批更新改變了游標時,Windows 會在整批更新完成後,依據指標下方的檢視套用新的形狀或 "none"。標題列與調整大小的邊框則保留系統的游標。

#鍵盤

按鍵作用
Return在 Input 中,依 submitBehavior 的設定而定:提交(onSubmit)、提交並離開欄位,或換行(多行輸入)
Shift+Return、Alt+Return在 Return 會提交的多行輸入中換行(Ctrl+Return 提交)
Tab、Shift+Tab在最上層的對話方塊內移至下一個/上一個文字欄位;欄位中的文字會被選取,與 Windows 的對話方塊相同
Escape離開已取得焦點的欄位;否則先清除文字選取,再關閉最上層的對話方塊或返回上一頁
Alt+Left、瀏覽器「上一頁」鍵、滑鼠上一頁按鈕返回(在欄位中也適用)
Ctrl+方向鍵、Home / End、Ctrl+Home / End,可搭配 Shift;Ctrl+Backspace / Delete在欄位中以字詞與行為單位移動、選取與刪除,與所有 Windows 應用程式相同
Ctrl+Z、Ctrl+Y、Ctrl+X / C / V / A;Shift+Delete、Ctrl+Insert、Shift+Insert復原、重做、剪下、複製、貼上、全選(masked 欄位不允許剪下與複製);Ctrl+A / Ctrl+C 可選取並複製可選取的文字
方向鍵;Page Up / Down、Space、Shift+Space;Home / End、Ctrl+Home / End捲動已取得焦點之檢視周圍或滑鼠下方的捲動檢視,單位為一行、一頁,或捲到開頭/結尾(欄位取得焦點時不適用)
F11、Alt+F4全螢幕、關閉視窗(nsh package 與 nsh run 應用程式)
Ctrl+R、F5重新載入最新的建置(nsh run)

快捷鍵可搭配任何鍵盤配置使用(在西里爾字母或希臘字母配置上,Ctrl+C 是在美式配置上會輸入 C 的那個按鍵)。用來確認輸入法組字(中文、日文等)的 Return 不會提交。onKeyPress 回報的內容與 React Native 相同:輸入的文字、"Enter"、"Backspace" 與 "Escape";貼上的文字不會被回報。

應用程式自己的快捷鍵(keymap)優先於已取得焦點之欄位的按鍵、導覽與捲動;Alt+F4、F11 與 Ctrl+Q 無法被綁定。

不同或不支援的項目:

  • WebView:位於具有 3D 轉換(透視)之檢視內的頁面不會顯示,且頁面上方的 backdropFilter 不會使其模糊。baseUrl 接受 http(s) URL。自帶視窗的原生元件會停留在 NSH 繪製的所有內容之上。
  • 影片:支援已安裝之編解碼器能解碼的格式;沒有海報圖;使用 controls 時控制列一律顯示。
  • 可縮放的捲動檢視(zoomTo、縮放屬性);捲動指示器無法拖曳。
  • mixBlendMode 與 backdropFilter 會被繪製,但在具有 filter 或 3D 轉換的檢視內則不會;borderCurve: "continuous" 會繪製圓形圓角。
  • 文字:頭部與中間的省略號僅適用於單行(多行則使用尾部省略號);彩色 emoji 僅支援 COLR 字型;小型大寫字母僅在字型提供時可用。
  • 文字輸入:鍵盤類型與自動填入不適用(沒有軟體鍵盤);Tab 會在文字欄位之間移動。按右鍵會開啟編輯選單(復原、剪下、複製、貼上、刪除、全選),可選取的文字則為(複製、全選)。
  • 無障礙功能(UI Automation):警示對話方塊與 toast 不會公開;標題一律回報為層級 1。
  • 輸入附屬檢視與狀態列(桌面平台上沒有對應的功能)。

#另請參閱