Questa pagina è stata tradotta automaticamente e può contenere errori. Leggi l'originale in inglese

Documentazione

macOS

Le app NSH funzionano anche come app Mac native. Il backend macOS le renderizza con view AppKit e Core Animation e dispone il testo con CoreText; il core, il grafo reattivo, il renderer, il layout Taffy e i moduli nativi sono gli stessi di iOS e Android, quindi un’unica app gira ovunque. Su macOS, os() è Os::MacOs (platform.os è "macos" in TSX) e valgono le prop desktop={{…}} di TSX. Il titolo della finestra, una barra del titolo propria dell’app, la barra dei menu e i menu contestuali provengono da nsh_core::element::desktop in Rust (Desktop title bars) e da nsh/desktop in TSX.

Il backend macOS si comporta come un’app desktop: input da mouse, trackpad e tastiera, finestre ridimensionabili, dialog sopra la finestra e una barra dei menu.

#Requisiti

  • macOS 14 o successivo, dove l’app viene eseguita.
  • Gli strumenti da riga di comando di Xcode (xcode-select --install), per compilare i runner delle app Rust e il runtime macOS di NSH da un checkout, e per creare il pacchetto delle app (codesign, sips). La distribuzione creata con cargo xtask dist su un Mac include il runtime delle app TSX in runtime/macos/.

#Eseguire un’app

Un’app Rust si esegue dalla propria directory:

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

nsh compila un runner di debug con il tuo crate e apre l’app in una finestra, con i suoi log (log::info! e il resto) nel terminale; una modifica al codice Rust del crate ricompila il runner e riavvia l’app. cargo run avvia la stessa root dal src/main.rs del crate, che nsh init --template rust scrive: run_app con AppSource::Rust.

Un’app TSX indica il proprio entry:

nsh run App.tsx --target macos

L’app si apre in una finestra AppKit (1200×800 per impostazione predefinita), con hot reload, log nel terminale e overlay degli errori come sulle altre piattaforme; Cmd+R ricarica l’ultima build. La barra dei menu e la finestra mostrano il nome dell’app ("name" di nsh.json, altrimenti quello della sua directory). In un checkout, nsh run compila prima l’host di sviluppo (nsh-macos-host). Interrompere nsh run chiude la finestra, e chiudere la finestra termina nsh run. L’host esegue anche da solo un bundle già compilato:

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 è la dimensione iniziale della finestra (app TSX); la finestra può essere ridimensionata. --display <n> (nsh run / nsh test --target macos --display <n>) centers it on display n: 1 è il display principale, quello con la barra dei menu.

nsh test --target macos esegue i test di un’app TSX in finestre macOS, su un display secondario quando ce n’è uno (lo schermo principale resta tuo; --display <n> ne sceglie un altro): ogni app aperta da un test ottiene una finestra propria, della dimensione che il test richiede anche su un display più basso, e i passi fanno clic, digitano e scorrono con l’input di sistema sulle view indicate dai loro selettori (Testing).

#Performance monitor

--fps (AppOptions { fps: true, .. } nel run_app di un’app Rust, AppView::set_fps_monitor(true) in caso di embedding, nsh run App.tsx --target macos --fps, il --fps dell’host, o il open -a MyApp --args --fps di un’app TSX di cui è stato creato il pacchetto) mostra un pannello in alto a destra nell’app che si aggiorna due volte al secondo:

RigaMisura
FPSFrame mostrati nell’ultimo secondo: aggiornamenti del display in cui l’app è cambiata (un’app inattiva ha valori bassi; non è lentezza). max è ciò a cui il main thread riusciva a stare dietro, poi la frequenza del display (120 Hz sugli schermi ProMotion).
TICKGli intervalli tra gli aggiornamenti del display così come sono arrivati (media, p95) e gap, l’intervallo p95 tra i frame mostrati: un aggiornamento (8,3 ms a 120 Hz) quando nulla è in ritardo.
MAINTempo del main thread per frame (applicazione delle modifiche del motore, disegno di testo e canvas, animazioni, input), il suo 95º percentile, e drop: la quota di frame che hanno richiesto più di un aggiornamento.
JS / COREPer commit: il tuo codice (handler, timer, righe di lista) e il core di NSH (layout, testo, diffing); commit al secondo, modifiche alle view native per commit.
HOPQuanto attendono le modifiche tra l’engine thread e il main thread; quanto è occupato l’engine thread.
CPU / GPU / MEMIl processo: CPU (100% è un core), la sua quota di GPU, la sua impronta di memoria (il valore di Activity Monitor).

Le barre sotto sono il tempo del main thread degli ultimi 120 frame rispetto a un aggiornamento (la linea): verde entro la metà, ambra entro la linea, rosso oltre. Mentre il pannello è visibile, le misure vengono anche registrate nel log una volta al secondo (target nsh::perf, nella forma del backend Linux con le fasi di frame di macOS tick, drain e canvas; millisecondi come media/p50/p95/max), che nsh test --fps -v mostra e examples/gallery/tools/perf.py legge. Misura le build release; il monitor non ha alcun costo quando è disattivato.

#Packaging

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

Scrive un bundle dell’app:

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

Il .app è firmato ad hoc, quindi gira sul Mac che lo ha compilato. Per distribuirlo, firmalo con un Developer ID e notarizzalo. --run apre l’app dopo averne creato il pacchetto.

#Embedding in un’app Mac

Il crate nsh-platform-macos (dal repository di NSH) offre a un’app Mac Rust AppView: un motore NSH come NSView. Ogni AppView è indipendente; più istanze possono girare contemporaneamente. Esegue una root Rust, il componente root del tuo 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 dà alla root il suo tema (nsh_base::Theme::from_json(json)?.into_tokens()) e Root::app_id l’id con cui vengono conservati i suoi dati. Un bundle TSX scritto da nsh build si carica nella stessa 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 sostituisce ciò che era in esecuzione; stop termina l’app. asset_base indica dove vengono risolti gli URI asset:/ (AssetBase::Directory o AssetBase::Http). I bundle bytecode (bundle.nshc) si caricano con unsafe { Bundle::bytecode(bytes) }: il bytecode è un input fidato, quindi caricalo solo dai file della tua app. Per un’app che è solo un’app NSH, run_app(source, options) (AppSource::Rust(root) o AppSource::Bundle(bundle)) apre un AppView in una finestra con un menu standard (About, Hide, Quit, Edit, Minimize, Zoom, Enter Full Screen, Close), che l’app può sostituire con Menu.setApplicationMenu (nsh/desktop), e resta in esecuzione finché la finestra non si chiude. Per impostazione predefinita, chiudere la finestra o scegliere Quit ferma il motore e ritorna da run_app; il chiamante può quindi completare la pulizia o rilanciare il proprio eseguibile.

Imposta AppOptions { keep_alive_on_close: true, ..Default::default() } per mantenere attiva la sessione standalone quando la sua finestra si chiude. La chiusura nasconde la finestra; riaprendola dal Dock si mostra la stessa finestra con lo stesso motore. Per un’uscita esplicita o un riavvio per aggiornamento, chiama nsh_platform_macos::quit_app() da qualsiasi thread. Chiude la finestra mantenuta, ferma il motore e ritorna da run_app. nsh_core::element::desktop::close_window() segue la policy scelta per la chiusura della finestra.

AppOptions::cache (o AppView::set_cache, per le app caricate successivamente) imposta dove le app conservano le proprie cache, le immagini che scaricano, e quanto ne conserva ciascuna (CacheOptions: la directory di cache del sistema e 256 MiB di immagini per impostazione predefinita).

AppView::set_locale(Some(locale)) e AppView::set_font_scale(Some(scale)) danno all’app la lingua e la dimensione del testo della tua app al posto di quelle del sistema (None torna a seguire il sistema): locale(), Intl, fontScale() e il testo si aggiornano subito (Device and environment).

AppView::set_theme_overrides(Some(json)) applica il marchio della tua app sopra il tema dell’app: un file di tema nel formato di nsh.json di "theme", i cui token prevalgono su quelli dell’app con gli stessi nomi in modalità chiara e scura mentre gli altri dell’app restano; classi come bg-primary cambiano stile subito, e le app caricate dopo partono con esso (None restituisce all’app il suo tema; un file che non viene analizzato è un errore e non cambia nulla).

I componenti nativi si registrano con AppView::register_component; vedi Native components. set_ui_sets(sets) imposta gli insiemi di UI che ogni motore serve: quelli di NSH (nsh_base::registry()) e quelli dei tuoi crate (my_app::registry(), che #[native_module] crea), così il TSX caricato da una view importa anche i tuoi componenti e moduli Rust.

#Cosa cambia su macOS

L’ambiente è quello di un desktop: la safe area e l’altezza della tastiera sono 0, idiom() è il desktop (platform.idiom è "desktop"), os_version() (platform.version) è la versione di macOS, e la modalità scura segue in tempo reale l’aspetto del sistema. Vedi Device and environment.

L’input è quello di un desktop: il mouse, il trackpad (scorrimento con inerzia, pinch e rotazione) e la tastiera. Il testo selezionabile si seleziona trascinando, con doppio e triplo clic, e si copia con Cmd+C. Escape, Option+Sinistra, Cmd+[ e il pulsante indietro del mouse chiudono il dialog più recente o tornano indietro in uno stack (Escape in un campo o in una pagina web con il focus lo abbandona soltanto; una pagina riceve prima questi tasti e mantiene quelli che usa); un Dialog è un livello sopra l’intera finestra che mostra il popup e lo sfondo dell’app. Le pagine mostrano il loro header in alto. Un clic in qualsiasi punto di un Input, padding compreso, posiziona il cursore nel punto più vicino. AlertDialog mostra uno sheet NSAlert sulla finestra: fino a due azioni di alert in riga (l’azione di annullamento più a sinistra, l’ultima azione regolare è quella predefinita per Invio), più azioni e le azioni di un action sheet incolonnate nel loro ordine con l’azione di annullamento per ultima e nessuna predefinita; Escape sceglie l’azione di annullamento.

#Tastiera

TastiAzione
ReturnIn un Input, ciò che dice submitBehavior: invia (onSubmit), invia e lascia il campo, oppure va a capo (input multilinea)
Return, Invio del tastierino numerico, SpazioAttiva una view con il focus tramite onPress una sola volta alla pressione del tasto; l’auto-repeat viene consumato
Shift+Return, Option+Return, Ctrl+ReturnVa a capo in un input multilinea il cui return invia
Tab, Shift+Tab (anche Ctrl)Il campo o la view premibile successivo / precedente nell’ordine dello schermo, all’interno del dialog più recente; Option+Tab digita una tabulazione in un input multilinea
EscapeAbbandona il campo con il focus; altrimenti cancella una selezione di testo, poi chiude il dialog più recente o torna indietro
Option+Sinistra, Cmd+[, pulsante indietro del mouseTornano indietro (non mentre un campo ha il focus: lì Option+Sinistra sposta il cursore)
Option / Cmd + frecce, con Shift; Option+Delete, Cmd+Delete; Ctrl+A / E / KSpostano, selezionano ed eliminano per parola e per riga in un campo, come in ogni app Mac
Cmd+Z, Shift+Cmd+Z, Cmd+X / C / V / AAnnulla, ripristina, taglia, copia, incolla, seleziona tutto (un campo masked rifiuta taglia e copia); Cmd+A / Cmd+C selezionano e copiano il testo selezionabile
Frecce; Page Up / Down, Spazio, Shift+Spazio, Option+Su / Giù; Home / End, Cmd+Su / GiùScorrono lo scroll view attorno alla view con il focus o sotto il mouse di una riga, di una pagina, all’inizio / alla fine (non mentre un campo ha il focus)
Cmd+W, Cmd+M, Ctrl+Cmd+F, Cmd+H, Option+Cmd+H, Cmd+QChiudi, riduci a icona, schermo intero, nascondi, nascondi altre, esci (menu delle app nsh package e nsh run)
Cmd+RRicarica l’ultima build (nsh run)

Le scorciatoie proprie dell’app (keymap) hanno la precedenza su queste, tranne i tasti di attivazione rivolti a una view premibile con il focus, Quit, Hide, Minimize e Full Screen, che non possono essere associati.

Le scorciatoie funzionano con qualsiasi layout di tastiera (su un layout cirillico o greco, Cmd+C è il tasto che digita C su un layout US). Un return che conferma la composizione di un metodo di input (cinese, giapponese, …) non invia mai. onKeyPress riporta ciò che riporta React Native: il testo digitato, "Enter", "Backspace" e "Escape".

Non supportato, o diverso:

  • Scroll view con zoom (zoomTo, le prop di zoom).
  • mixBlendMode, e brightness() superiore a 1.
  • Input: tipi di contenuto e autofill, tipi di tastiera, maiuscole automatiche, spaziatura tra le lettere; le input accessory view non vengono mai mostrate (non c’è una tastiera software) e keyboardShouldPersistTaps viene ignorato.
  • scrollable={false} di WebView impedisce alla rotellina del mouse e al trackpad di scorrere la pagina: scorrono gli scroll view dell’app attorno ad essa, come su iOS. La pagina scorre comunque con la tastiera (Spazio, frecce) e con i propri script, e anche gli swipe del trackpad tra le sue pagine (allowsBackForwardNavigationGestures) si interrompono.
  • Accessibilità: accessibilityActions personalizzati.
  • La status bar e l’haptics (nessun equivalente desktop).

#Vedi anche