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

文件

macOS

NSH 應用程式亦可作為原生 Mac 應用程式執行。macOS 後端以 AppKit view 和 Core Animation 渲染應用程式,並以 CoreText 排版文字;核心、響應式圖(reactive graph)、渲染器、Taffy 佈局和原生模組都與 iOS 和 Android 相同,因此同一個應用程式可在所有平台執行。在 macOS 上,os() 是 Os::MacOs(TSX 中的 platform.os 是 "macos"),TSX 的 desktop={{…}} props 同樣適用。視窗標題、應用程式自訂的標題列、選單列和右鍵選單,分別來自 Rust 中的 nsh_core::element::desktop(桌面標題列)和 TSX 中的 nsh/desktop。

macOS 後端的行為與桌面應用程式一致:支援滑鼠、觸控板和鍵盤輸入、可調整大小的視窗、覆蓋在視窗上的對話框,以及選單列。

#系統需求

  • macOS 14 或更新版本,即應用程式執行的環境。
  • Xcode 的命令列工具(xcode-select --install),用於從 checkout 建置 Rust 應用程式的 runner 和 NSH 的 macOS 執行環境,以及打包應用程式(codesign、sips)。在 Mac 上以 cargo xtask dist 製作的發行版,會把 TSX 應用程式的執行環境附帶於 runtime/macos/ 中。

#執行應用程式

Rust 應用程式在其目錄中執行:

nsh run --target macos         # the default on a Mac

nsh 會連同你的 crate 建置 debug runner,並在視窗中開啟應用程式,其日誌(log::info! 等)會顯示在終端機中;crate 的 Rust 程式碼一有變更,就會重新建置 runner 並重新啟動應用程式。cargo run 會從 crate 的 src/main.rs(由 nsh init --template rust 寫入)啟動同一個根:run_app,搭配 AppSource::Rust。

TSX 應用程式需指明其入口:

nsh run App.tsx --target macos

應用程式會在 AppKit 視窗中開啟(預設 1200×800),並支援熱重載、終端機日誌和錯誤覆蓋層,與其他目標平台相同;按 Cmd+R 可重新載入最新的建置。選單列和視窗會顯示應用程式的名稱(nsh.json 的 "name",否則為其目錄名稱)。在 checkout 中,nsh run 會先建置開發用 host(nsh-macos-host)。停止 nsh run 會關閉視窗,關閉視窗則會結束 nsh run。host 亦可獨立執行已建置的 bundle:

nsh build my-app --release -o build
dist/nsh/runtime/macos/nsh-macos-host --bundle build/bundle.nshc --window-size 1200x800   # in a checkout: target/debug/nsh-macos-host

--window-size 是視窗的初始大小(TSX 應用程式);視窗可以調整大小。--display <n>(nsh run / nsh test --target macos --display <n>) centers it on display n: 1 為主顯示器,即設有選單列的那個。

nsh test --target macos 會在 macOS 視窗中執行 TSX 應用程式的測試,如有第二個顯示器便使用該顯示器(主螢幕仍由你使用;--display <n> 可選擇其他顯示器):測試開啟的每個應用程式都會有獨立的視窗,大小依測試要求而定,即使顯示器比該大小更矮也一樣;各個步驟會以系統輸入方式,在其選擇器所指的 view 上點按、輸入和捲動(測試)。

#效能監察器

--fps(Rust 應用程式的 run_app 中的 AppOptions { fps: true, .. }、嵌入時的 AppView::set_fps_monitor(true)、nsh run App.tsx --target macos --fps、host 的 --fps,或已打包 TSX 應用程式的 open -a MyApp --args --fps)會在應用程式右上方顯示一個每秒更新兩次的面板:

列 量度內容
FPS 過去一秒內顯示的幀數:即應用程式有變化的顯示器更新次數(閒置的應用程式讀數偏低,這並不代表速度慢)。max 是主執行緒能夠跟上的幀率,之後是顯示器的更新率(ProMotion 螢幕為 120 Hz)。
TICK 顯示器更新之間實際到達的間隔(平均值、p95),以及 gap,即已顯示幀之間的 p95 間隔:沒有任何延遲時為一次更新的時間(120 Hz 下為 8.3 ms)。
MAIN 每幀的主執行緒時間(套用引擎的變更、繪製文字和畫布、動畫、輸入)、其第 95 百分位數,以及 drop:耗時超過一次更新的幀所佔的比例。
JS / CORE 每次 commit:你的程式碼(處理器、計時器、列表列)和 NSH 的核心(佈局、文字、diff)所用的時間;每秒 commit 數、每次 commit 的原生 view 變更數。
HOP 變更在引擎執行緒與主執行緒之間的等待時間;引擎執行緒的繁忙程度。
CPU / GPU / MEM 行程:CPU(100% 為一個核心)、其 GPU 佔用比例、其記憶體佔用量(即 Activity Monitor 顯示的數字)。

下方的長條是最近 120 幀的主執行緒時間與一次更新時間(那條線)的比較:在一半以內為綠色,在一次更新以內為琥珀色,超出則為紅色。面板顯示期間,讀數亦會每秒記錄一次(目標 nsh::perf,格式與 Linux 後端相同,但使用 macOS 的幀階段 tick、drain 和 canvas;毫秒以平均值/p50/p95/最大值表示),可由 nsh test --fps -v 顯示,亦可由 examples/gallery/tools/perf.py 讀取。請以 release 建置進行量度;監察器關閉時不會產生任何開銷。

#打包

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

這會寫出一個應用程式 bundle:

.nsh/out/<Name>.app/Contents/
  Info.plist
  MacOS/<Name>               the executable: a Rust app's own runner, built with its crate; nsh's release runner for TSX
  Resources/nsh/bundle.nshc  a TSX app, compiled
  Resources/nsh/assets/…     the assets it imports (a Rust crate's `assets!()` too)
  Resources/nsh/app.json     the window title and the app's appearance
  Resources/AppIcon.icns     the icon, when nsh.json names one (converted with sips)

.app 以 ad hoc 方式簽署,因此只能在建置它的 Mac 上執行。若要發佈,請以 Developer ID 簽署並進行公證(notarize)。--run 會在打包後開啟應用程式。

#嵌入 Mac 應用程式

nsh-platform-macos crate(來自 NSH 儲存庫)為 Rust Mac 應用程式提供 AppView:一個作為 NSView 的 NSH 引擎。每個 AppView 都是獨立的,可同時執行多個。它會執行 Rust 根,即你的 crate 的根元件:

use std::rc::Rc;
use nsh_platform_macos::{
    objc2::MainThreadMarker, objc2_app_kit::NSWindow, set_ui_sets, AppOptions, AppView, Root,
};

fn show(window: &NSWindow) -> Rc<AppView> {
    // 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 mtm = MainThreadMarker::new().expect("main thread");
    let view = AppView::new(mtm, AppOptions::default());
    // register native components here, before loading
    view.load(Root::of::<my_app::App>().into());
    window.setContentView(Some(&view.view()));
    // Keep the view while the window shows it; `stop` it when the window closes.
    view
}

Root::tokens 為根提供其主題(nsh_base::Theme::from_json(json)?.into_tokens()),Root::app_id 則是其資料保存時使用的 id。由 nsh build 寫出的 TSX bundle 可載入到同一個 view 中:

use nsh_platform_macos::{AppOptions, AppView, AssetBase, Bundle};

let view = AppView::new(mtm, AppOptions {
    asset_base: Some(AssetBase::Directory("app/assets".into())),
    ..AppOptions::default()
});
let code = std::fs::read_to_string("app/bundle.js").expect("bundle");
view.load(Bundle::source(code).into());

AppView::load 會取代原本執行的內容;stop 會結束應用程式。asset_base 指明 asset:/ URI 的解析位置(AssetBase::Directory 或 AssetBase::Http)。位元組碼 bundle(bundle.nshc)以 unsafe { Bundle::bytecode(bytes) } 載入:位元組碼屬受信任的輸入,因此只應從你自己應用程式的檔案載入。 對於純粹是 NSH 的應用程式,run_app(source, options)(AppSource::Rust(root) 或 AppSource::Bundle(bundle))會在視窗中開啟一個 AppView,並附有標準選單(About、Hide、Quit、Edit、Minimize、Zoom、Enter Full Screen、Close),應用程式可以用 Menu.setApplicationMenu(nsh/desktop)取代這個選單,並一直執行到視窗關閉為止。 預設情況下,關閉視窗或選擇 Quit 會停止引擎,並從 run_app 返回; 之後呼叫者可以完成清理工作,或重新啟動其可執行檔。

設定 AppOptions { keep_alive_on_close: true, ..Default::default() } 可在視窗關閉時讓 獨立會話繼續存活。關閉時只會隱藏視窗; 從 Dock 重新開啟時,會顯示同一個視窗和引擎。若要明確結束(Exit) 或為更新而重新啟動,請從任何執行緒呼叫 nsh_platform_macos::quit_app()。它 會關閉保留的視窗、停止引擎,並從 run_app 返回。 nsh_core::element::desktop::close_window() 遵循所選的視窗關閉策略。

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 等 class 會隨即重新套用樣式,之後載入的應用程式也會以它啟動 (None 會讓應用程式恢復使用自己的主題;無法解析的檔案屬錯誤,不會造成任何變更)。

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

#macOS 的差異

環境與桌面電腦相同:安全區域和鍵盤高度為 0,idiom() 是桌面(platform.idiom 是 "desktop"),os_version()(platform.version)是 macOS 版本,而深色模式會即時跟隨系統的外觀。請參閱 裝置與環境。

輸入方式與桌面電腦相同:滑鼠、觸控板(帶慣性的捲動、縮放和旋轉)和鍵盤。 可選取的文字可以用拖曳、按兩下和按三下來選取,並以 Cmd+C 複製。Escape、Option+Left、 Cmd+[ 和滑鼠的返回按鈕會關閉最新的對話框,或在 stack 中返回(在獲得焦點的輸入欄位或網頁中,Escape 只會離開該欄位或網頁;網頁會先收到這些按鍵,並保留它所用到的按鍵);Dialog 是覆蓋整個視窗的一層,用來顯示應用程式的彈出視窗和背景遮罩。頁面會在頂部顯示其 header。在 Input 內任何位置按一下(包括其 padding),都會把插入點放在最接近的位置。AlertDialog 會在視窗上顯示 NSAlert sheet:警示最多可並排顯示兩個動作(取消動作在最左邊,最後一個一般動作為 Return 的預設動作);更多的動作以及 action sheet 的動作,則按順序垂直堆疊,取消動作排在最後且沒有預設動作;按 Escape 會選擇取消動作。

#鍵盤

按鍵 作用
Return 在 Input 中,依 submitBehavior 的設定:提交(onSubmit)、提交並離開欄位,或換行(多行輸入)
Return、小鍵盤 Enter、Space 在按鍵按下時,以 onPress 啟動獲得焦點的 view 一次;自動重複的按鍵會被忽略
Shift+Return、Option+Return、Ctrl+Return 在以 Return 提交的多行輸入中換行
Tab、Shift+Tab(Ctrl 亦可) 在最新的對話框內,按畫面順序移至下一個 / 上一個欄位或可按壓的 view;Option+Tab 會在多行輸入中輸入 tab
Escape 離開獲得焦點的欄位;否則先清除文字選取,再關閉最新的對話框或返回
Option+Left、Cmd+[、滑鼠返回按鈕 返回(欄位取得焦點時不適用:此時 Option+Left 會移動插入點)
Option / Cmd + 方向鍵(可加 Shift);Option+Delete、Cmd+Delete;Ctrl+A / E / K 在欄位中按詞和按行移動、選取和刪除,與所有 Mac 應用程式一樣
Cmd+Z、Shift+Cmd+Z、Cmd+X / C / V / A 還原、重做、剪下、複製、貼上、全選(masked 欄位不允許剪下和複製);Cmd+A / Cmd+C 可選取和複製可選取的文字
方向鍵;Page Up / Down、Space、Shift+Space、Option+Up / Down;Home / End、Cmd+Up / Down 捲動獲得焦點的 view 周圍或滑鼠下方的捲動 view,按行、按頁或移至開頭 / 結尾(欄位取得焦點時不適用)
Cmd+W、Cmd+M、Ctrl+Cmd+F、Cmd+H、Option+Cmd+H、Cmd+Q 關閉、最小化、全螢幕、隱藏、隱藏其他、結束(nsh package 和 nsh run 應用程式的選單)
Cmd+R 重新載入最新的建置(nsh run)

應用程式自己的快速鍵(keymap)優先於以上按鍵,但發給獲得焦點的可按壓 view 的啟動按鍵,以及 Quit、Hide、Minimize 和 Full Screen 除外,這些按鍵無法被綁定。

快速鍵適用於任何鍵盤配置(在西里爾字母或希臘字母配置上,Cmd+C 是在美式配置上輸入 C 的那個鍵)。 用於確認輸入法組字(中文、日文等)的 Return,絕不會提交內容。onKeyPress 回報的內容與 React Native 一致:輸入的文字、"Enter"、"Backspace" 和 "Escape"。

不支援或有所不同的項目:

  • 可縮放的捲動 view(zoomTo、縮放相關的 props)。
  • mixBlendMode,以及大於 1 的 brightness()。
  • 輸入:內容類型和自動填寫、鍵盤類型、自動大寫、字母間距;輸入附屬 view 不會顯示(沒有軟件鍵盤),而 keyboardShouldPersistTaps 會被忽略。
  • WebView 的 scrollable={false} 會阻止滑鼠滾輪和觸控板捲動頁面:它們改為捲動 頁面周圍的應用程式捲動 view,與 iOS 相同。頁面仍可用鍵盤(Space、方向鍵)和其自身的 腳本捲動,而在其頁面之間以觸控板滑動切換(allowsBackForwardNavigationGestures)亦同樣會停止。
  • 無障礙功能:自訂 accessibilityActions。
  • 狀態列和觸覺回饋(桌面平台沒有對應功能)。

#另請參閱