تُرجمت هذه الصفحة آليًا وقد تحتوي على أخطاء. اقرأ الأصل الإنجليزي

التوثيق

macOS

تعمل تطبيقات NSH أيضًا كتطبيقات Mac أصلية. تعرضها واجهة macOS الخلفية باستخدام عروض AppKit وCore Animation، وتضبط تخطيط النص باستخدام CoreText؛ أما النواة والرسم البياني التفاعلي والمُصيِّر وتخطيط Taffy والوحدات الأصلية فهي نفسها على iOS وAndroid، ولذلك يعمل التطبيق الواحد في كل مكان. على macOS، os() هو Os::MacOs (وplatform.os هو "macos" في TSX) وتنطبق خصائص desktop={{…}} في TSX. يأتي عنوان النافذة وشريط عنوان خاص بالتطبيق وشريط القوائم والقوائم السياقية من nsh_core::element::desktop في Rust (Desktop title bars) ومن nsh/desktop في TSX.

تتصرف واجهة macOS الخلفية كتطبيق سطح مكتب: إدخال بالماوس ولوحة التتبع ولوحة المفاتيح، ونوافذ قابلة لتغيير الحجم، وحوارات فوق النافذة، وشريط قوائم.

#المتطلبات

  • macOS 14 أو أحدث، حيث يعمل التطبيق.
  • أدوات سطر الأوامر في Xcode (xcode-select --install)، لبناء مشغِّلات تطبيقات Rust ووقت تشغيل NSH لنظام macOS من نسخة مستنسخة من المستودع، ولحزم التطبيقات (codesign، sips). تتضمن النسخة الموزَّعة التي تُنشأ باستخدام cargo xtask dist على جهاز Mac وقت تشغيل تطبيقات TSX في runtime/macos/.

#تشغيل تطبيق

يُشغَّل تطبيق Rust من مجلده:

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

يبني nsh مشغِّلًا لأغراض التنقيح مع الـ crate الخاص بك ويفتح التطبيق في نافذة، مع سجلاته (log::info! وغيره) في الطرفية؛ وأي تغيير في شيفرة Rust الخاصة بالـ crate يعيد بناء المشغِّل ويعيد تشغيل التطبيق. يبدأ cargo run الجذر نفسه من src/main.rs الخاص بالـ crate، والذي يكتبه nsh init --template rust: run_app مع AppSource::Rust.

يحدد تطبيق TSX نقطة دخوله:

nsh run App.tsx --target macos

يُفتح التطبيق في نافذة AppKit (بحجم 1200×800 افتراضيًا)، مع إعادة تحميل فورية، وسجلات في الطرفية، وطبقة الأخطاء كما في الأهداف الأخرى؛ ويعيد Cmd+R تحميل أحدث بناء. يعرض شريط القوائم والنافذة اسم التطبيق ("name" في nsh.json، وإلا اسم مجلده). في النسخة المستنسخة من المستودع، يبني nsh run مضيف التطوير (nsh-macos-host) أولًا. يؤدي إيقاف nsh run إلى إغلاق النافذة، ويؤدي إغلاق النافذة إلى إنهاء nsh run. يشغِّل المضيف أيضًا حزمة مبنية بمفرده:

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 اختبارات تطبيق TSX في نوافذ macOS، على شاشة ثانوية إن وجدت (تبقى الشاشة الرئيسية لك؛ ويختار --display <n> شاشة أخرى): يحصل كل تطبيق يفتحه الاختبار على نافذة خاصة به، بالحجم الذي يطلبه الاختبار حتى على شاشة أقصر منه، وتنقر الخطوات وتكتب وتمرر بإدخال النظام على العروض التي تسميها محدداتها (Testing).

#مراقب الأداء

يعرض --fps (AppOptions { fps: true, .. } في run_app الخاص بتطبيق Rust، أو AppView::set_fps_monitor(true) عند التضمين، أو nsh run App.tsx --target macos --fps، أو --fps الخاص بالمضيف، أو open -a MyApp --args --fps الخاص بتطبيق TSX مُحزَّم) لوحة في أعلى يمين التطبيق تتحدث مرتين في الثانية:

الصفما يقيسه
FPSالإطارات المعروضة خلال الثانية الأخيرة: دورات تحديث الشاشة التي تغيّر فيها التطبيق (التطبيق الخامل يعطي قراءة منخفضة؛ وهذا لا يعني بطئًا). max هو ما استطاع الخيط الرئيسي مواكبته، ثم معدل الشاشة (120 Hz على شاشات ProMotion).
TICKالفجوات بين دورات تحديث الشاشة كما وصلت (المتوسط، p95) وgap، أي فجوة p95 بين الإطارات المعروضة: دورة تحديث واحدة (8.3 ms عند 120 Hz) حين لا يتأخر شيء.
MAINزمن الخيط الرئيسي لكل إطار (تطبيق تغييرات المحرك، ورسم النصوص واللوحات، والحركات، والإدخال)، ونسبته المئوية 95، وdrop: حصة الإطارات التي استغرقت أكثر من دورة تحديث واحدة.
JS / COREلكل commit: شيفرتك (المعالجات والمؤقتات وصفوف القوائم) ونواة NSH (التخطيط والنص ومقارنة الفروق)؛ وعدد commits في الثانية، وعدد تغييرات العروض الأصلية لكل commit.
HOPالمدة التي تنتظرها التغييرات بين خيط المحرك والخيط الرئيسي؛ ومدى انشغال خيط المحرك.
CPU / GPU / MEMالعملية: المعالج (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

يكتب حزمة تطبيق:

.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

يوفّر الـ crate nsh-platform-macos (من مستودع NSH) لتطبيق Mac المكتوب بـ Rust الأداة AppView: محرك NSH واحد كـ NSView. كل 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 المعرّف الذي تُحفظ تحته بياناته. وتُحمَّل حزمة TSX التي كتبها nsh build في العرض نفسه:

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 المكان الذي تُحلّ فيه معرّفات URI من نوع asset:/ (AssetBase::Directory أو AssetBase::Http). تُحمَّل حزم الـ bytecode (bundle.nshc) باستخدام unsafe { Bundle::bytecode(bytes) }: الـ bytecode مُدخل موثوق، لذا لا تحمّله إلا من ملفات تطبيقك أنت. أما التطبيق الذي ليس سوى تطبيق 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() والنص ذلك فورًا (Device and environment).

يضع AppView::set_theme_overrides(Some(json)) علامة تطبيقك التجارية فوق سمة التطبيق: ملف سمة بصيغة "theme" الخاص بـ nsh.json، تتغلب رموزه (tokens) على رموز التطبيق التي تحمل الأسماء نفسها في الوضعين الفاتح والداكن بينما تبقى رموز التطبيق الأخرى؛ وتُعاد تهيئة أنماط الفئات مثل bg-primary فورًا، وتبدأ التطبيقات المحمَّلة لاحقًا به (يعيد None للتطبيق سمته الخاصة؛ والملف الذي لا يُحلَّل يُعدّ خطأ ولا يغيّر شيئًا).

تُسجَّل المكوّنات الأصلية باستخدام AppView::register_component؛ انظر Native components. يحدد set_ui_sets(sets) مجموعات واجهة المستخدم التي يخدمها كل محرك: مجموعات NSH (nsh_base::registry()) ومجموعات الـ crates الخاصة بك (my_app::registry()، التي يُنشئها #[native_module])، بحيث تستورد شيفرة TSX التي يحمّلها العرض مكوّنات Rust ووحداتها الخاصة بك أيضًا.

#ما يختلف على macOS

البيئة هي بيئة سطح المكتب: المنطقة الآمنة وارتفاع لوحة المفاتيح يساويان 0، وidiom() هو سطح المكتب (platform.idiom هو "desktop")، وos_version() (platform.version) هو إصدار macOS، ويتبع الوضع الداكن مظهر النظام مباشرة. انظر Device and environment.

الإدخال هو إدخال سطح المكتب: الماوس، ولوحة التتبع (التمرير بالزخم، والقرص والتدوير)، ولوحة المفاتيح. يُحدَّد النص القابل للتحديد بالسحب والنقر المزدوج والثلاثي، ويُنسخ بـ Cmd+C. تغلق Escape وOption+Left وCmd+[ وزر الرجوع في الماوس أحدثَ حوار أو ترجع خطوة في المكدس (تؤدي Escape في حقل أو صفحة ويب لها التركيز إلى الخروج منها فقط؛ فالصفحة تحصل على هذه المفاتيح أولًا، وتحتفظ بما تستخدمه منها)؛ وDialog هو طبقة فوق النافذة كلها تعرض النافذة المنبثقة للتطبيق وخلفيتها. تعرض الصفحات header في أعلاها. النقر في أي مكان داخل Input، بما في ذلك الحشوة (padding)، يضع المؤشر عند أقرب نقطة. يعرض AlertDialog ورقة NSAlert على النافذة: حتى إجراءي تنبيه في صف واحد (إجراء الإلغاء في أقصى اليسار، وآخر إجراء عادي هو الافتراضي لمفتاح Return)، أما الإجراءات الأكثر وإجراءات ورقة الإجراءات فتُكدَّس بترتيبها مع إجراء الإلغاء أخيرًا ودون إجراء افتراضي؛ وتختار Escape إجراء الإلغاء.

#لوحة المفاتيح

المفاتيحالفعل
Returnفي Input، ما يقوله submitBehavior: إرسال (onSubmit)، أو إرسال ومغادرة الحقل، أو سطر جديد (حقول الإدخال متعددة الأسطر)
Return، Enter في لوحة الأرقام، Spaceتفعيل عرض له التركيز عبر onPress مرة واحدة عند الضغط؛ ويُستهلك التكرار التلقائي
Shift+Return، Option+Return، Ctrl+Returnسطر جديد في حقل متعدد الأسطر يرسل عند Return
Tab، Shift+Tab (وCtrl أيضًا)الحقل أو العرض القابل للضغط التالي / السابق بترتيب الشاشة، ضمن أحدث حوار؛ ويكتب 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تمرير عرض التمرير المحيط بالعرض الذي له التركيز أو الذي تحت الماوس بمقدار سطر أو صفحة أو إلى البداية / النهاية (ليس أثناء تركيز حقل)
Cmd+W، Cmd+M، Ctrl+Cmd+F، Cmd+H، Option+Cmd+H، Cmd+Qإغلاق، تصغير، ملء الشاشة، إخفاء، إخفاء الآخرين، إنهاء (قوائم تطبيقات nsh package وnsh run)
Cmd+Rيعيد تحميل أحدث بناء (nsh run)

تأتي اختصارات التطبيق نفسه (keymap) قبل هذه، باستثناء مفاتيح التفعيل الموجهة إلى عرض قابل للضغط له التركيز، وQuit وHide وMinimize وFull Screen، التي لا يمكن ربطها.

تعمل الاختصارات مع أي تخطيط لوحة مفاتيح (في التخطيط السيريلي أو اليوناني، يكون Cmd+C هو المفتاح الذي يكتب C في التخطيط الأمريكي). والـ return الذي يثبّت تأليف طريقة إدخال (الصينية، اليابانية، …) لا يرسل أبدًا. يبلّغ onKeyPress بما يبلّغ به React Native: النص المكتوب، و"Enter"، و"Backspace"، و"Escape".

غير مدعوم، أو مختلف:

  • عروض التمرير القابلة للتكبير (zoomTo، وخصائص التكبير).
  • mixBlendMode، وbrightness() الأكبر من 1.
  • حقول الإدخال: أنواع المحتوى والملء التلقائي، وأنواع لوحة المفاتيح، والكتابة بالأحرف الكبيرة تلقائيًا، وتباعد الأحرف؛ ولا تُعرض عروض ملحقات الإدخال أبدًا (لا توجد لوحة مفاتيح برمجية) ويُتجاهل keyboardShouldPersistTaps.
  • scrollable={false} الخاص بـ WebView يمنع عجلة الماوس ولوحة التتبع من تمرير الصفحة: بل تمرران عروض التمرير في التطبيق المحيطة بها، كما في iOS. وما زالت الصفحة تتمرر بلوحة مفاتيحها (Space والأسهم) وبسكربتاتها الخاصة، وتتوقف أيضًا سحبات لوحة التتبع بين صفحاتها (allowsBackForwardNavigationGestures).
  • إمكانية الوصول: accessibilityActions المخصصة.
  • شريط الحالة والاهتزاز اللمسي (لا مكافئ لهما على سطح المكتب).

#انظر أيضًا