حالُ الهواتفِ في لغة ص
الخلاصةُ في سطر
لا تطبيقَ هاتفٍ يُبنى من لغةِ ص اليومَ بلا مرورٍ بأدواتِ المنصّة. والموجودُ ليس فراغًا: مولِّدا مصدرٍ يُخرِجانِ من شجرةِ واجهةِ ص نفسِها شفرةَ Jetpack Compose لأندرويدَ وشفرةَ SwiftUI لـiOS.
وهذه الصفحةُ تسمّي ما يوجدُ وما لا يوجدُ وما يفتحُ المجال — لا أكثرَ ولا أقلّ.
ما يوجدُ فعلًا
مولِّدانِ مُعلَنانِ مستقرَّينِ في UICore، ومقيسانِ بالترجمةِ والتشغيل:
متغير الجذر = عمود(
نص_عنصر("مرحبًا")،
زر("انقرني")
)
اطبع_سطر(توليد_أندرويد(الجذر، "شاشتي"))
اطبع_سطر(توليد_آي_أو_إس(الجذر، "شاشتي"))المخرَجُ المقيسُ لأندرويد — دالّةُ @Composable كاملةٌ مع @Preview:
@Composable
fun شاشتي() {
Column(modifier = Modifier) {
Text(text = "مرحبًا", modifier = Modifier)
Button(onClick = {}, ...) { Text("انقرني") }
}
}ولـiOS — بنيةُ View مع اتّجاهٍ من اليمينِ إلى اليسارِ مضبوطًا:
struct شاشتي: View {
var body: some View {
VStack {
Text("مرحبًا")
Button(action: {}) { Text("انقرني") }
}
.environment(\.layoutDirection, .rightToLeft)الشجرةُ الواحدةُ تُوصَفُ مرّةً وتُترجَمُ إلى المنصّتَين. وهذا مكسبٌ حقيقيّ: بنيةُ الشاشةِ وخصائصُها وترتيبُها ونصوصُها العربيّةُ تُنقَلُ بلا إعادةِ كتابة.
ما لا يوجدُ — بلا مواربة
مولِّدُ المصدرِ ليس مُصيِّرًا حيًّا
هو يُرجِعُ نصًّا (نوع(…) == "نص" — مقيس) ثمّ يقف. لا يرسمُ، ولا يُشغِّل، ولا يملكُ حلقةَ أحداث. والبناءُ والتصييرُ يبقيانِ على Android Studio أو Xcode. تفصيلُ الفرق.
والمنطقُ لا يُنقَل. مقيسٌ في المخرَجِ أعلاه: الزرُّ المربوطُ بمعالِجٍ في ص يخرجُ بـonClick = {} وaction: {} فارغَين. فالمولَّدُ نقطةُ بدايةٍ بنيويّة، لا تطبيقٌ يعمل.
وطبقةُ المنصّةِ دَينٌ مُعلَن. UIPlatform مُعلَنٌ ٤٣ مدخلًا واثنانِ منها مستقرّان (ارسم_خط · ارسم_مستطيل)؛ والحوارُ (UIDialog) والتخزينُ (UIStorage) والجهازُ (UIDevice، ١٨ مدخلًا) صفرُ مستقرٍّ في كلٍّ منها. فالنوافذُ والمدخلاتُ ودورةُ الحياةِ غيرُ موصولةٍ بالمترجِّم.
enabled: true لا يُقرَأُ قدرة
language-truth/ui_nodes.yaml يُعلِنُ مُصيِّرَي أندرويدَ وiOS enabled: true. وهذا الحقلُ لم يُقَسْ صدقُه، ولا تُشتَقُّ منه دعوى.
والشاهدُ في الملفِّ عينِه: خلفيّةُ ماكِ مُعلَنةٌ فيه بالحقلِ نفسِه enabled: false، وسببُها مكتوبٌ حرفيًّا — تستدعي طبقةَ تفاعلٍ لا تصرّحُ بها ترويستُها فلا تُترجَمُ كما هي. فالحقلُ يصفُ نيّةَ الشفرةِ ويجوزُ أن ينحرفَ عنها.
ولذلك بطاقةُ هذا المجالِ خاليةُ الدعاوى (capabilities: [] في tracks/mobile.yaml) مع أنّ في مصدرِ الحقيقةِ ما يُغري بملئِها. ولو مُلِئَت بدعوى فضاءِ أسماءٍ فيه مدخلٌ مستقرٌّ واحدٌ لانقلبتِ الدرجةُ إلى «ناشئ» بلا أن تتغيّرَ قدرةٌ واحدة — وذلك هو الكذبُ الذي وُجِدَت هذه البنيةُ لمنعِه.
ما يفتحُ المجال — ثلاثُ خطواتٍ محدَّدة
| # | الخطوة | الأثر |
|---|---|---|
| ① | وصلُ مدخلاتِ UIPlatform بالمترجِّمِ ورفعُ وسمِ التأجيلِ عنها | ٤٣ ← تصيرُ الطبقةُ التي تحتَ العنصرِ قدرةً لا دَينًا |
| ② | بناءُ مُصيِّرٍ حيٍّ لإحدى المنصّتَينِ وتشغيلُه على جهازٍ حقيقيّ | يصيرُ للمجالِ دعوى قدرةٍ واحدةٌ صادقة |
| ③ | حارسٌ يقيسُ البناءَ فيُحمِّرُ إن أُعلِنَت خلفيّةٌ ولم تُترجَمْ | enabled يصيرُ حقلًا مقيسًا لا نيّةً مكتوبة |
ولماذا هذا ترتيبٌ لا قائمةَ أمنيات
النظيرُ قائمٌ في الشجرةِ فعلًا: scripts/codegen/check_ui_render_completeness.py يقيسُ تغطيةَ العُقَدِ في كلِّ هدفِ تصيير. فالخطوةُ ③ توسيعُ حارسٍ يعملُ لا اختراعُ آلة.
ولحظةَ يُبنى مُصيِّرٌ ويُشغَّلُ على جهاز، تُكتَبُ دعوى واحدةٌ صادقةٌ في tracks/mobile.yaml فتنقلبُ درجةُ البطاقةِ من تلقاءِ نفسِها في البناءِ التالي. هذا هو معنى «قابلةٌ للتوسّع» عمليًّا.
أينَ يُبنى تطبيقُ واجهةٍ اليومَ
على سطحِ المكتب، وهو مُصيِّرٌ حيٌّ مقيس: النافذةُ تُفتَحُ وحلقةُ الأحداثِ تعمل. ابدَأْ من أوّلِ نافذةٍ وزرٍّ يستجيب، ثمّ ولِّدْ من الشجرةِ نفسِها شفرةَ Compose أو SwiftUI حينَ تحتاجُ نقطةَ بدايةٍ على الهاتف.