شجرةٌ واحدةٌ ومخارجُ كثيرة — ولماذا يهمُّ الفرق
الشجرةُ تُعلَنُ مرّةً
language-truth/ui_nodes.yaml يحملُ الجردَ الكاملَ لأنواعِ عُقَدِ الواجهة — ١١٠ عقدةً — ومنه يُولَّدُ رأسُ X-macro الذي تستهلكُه مكتبةُ الرسوميّاتِ نفسُها. فلا اسمَ نوعِ عقدةٍ حرفيٌّ في شفرةِ المترجِّم، ولا نسخةَ تعدادٍ ثانية. ونظيرُه في الأحداثِ (ui_events.yaml) والخصائصِ (ui_props.yaml) والمعدّلاتِ (ui_modifiers.yaml) والانتشارِ والحركاتِ والمنحنيات.
فحينَ تكتبُ عمود(نص_عنصر("مرحبًا")، زر("انقرني")) أنتَ لا تخاطبُ نظامَ نوافذَ بعينِه: أنتَ تُنشئُ عُقَدًا في تلك الشجرة. وما بعدَها هو الفرقُ الذي تقومُ عليه هذه الصفحةُ كلُّها.
المخرَجانِ نوعانِ لا نوعٌ واحد
الشجرةُ الواحدةُ تُغذّي صنفَينِ مختلفَينِ اختلافًا جوهريًّا:
مُصيِّرٌ حيّ — يرسمُ بكسلًا
يفتحُ سطحَ عرضٍ، ويرسمُ العُقَدَ عليه، ويملكُ حلقةَ الأحداث: يتلقّى النقرةَ ويوجّهُها إلى العقدةِ الهدفِ ويستدعي معالِجَك. نقطةُ الدخولِ إليه تشغيل_تطبيق(الجذر) — وهي لا ترجعُ حتّى تُغلَقَ النافذة.
مولِّدُ مصدر — يُخرِجُ نصَّ شفرة
يمشي على الشجرةِ نفسِها فيُخرِجُ سلسلةً نصّيّة: HTML أو Kotlin أو Swift. ثمّ يقف. لا يرسمُ شيئًا، ولا يُشغِّلُ شيئًا، ولا يملكُ حلقةَ أحداث. البناءُ والتصييرُ يقعانِ على أدواتِ تلك المنصّةِ ومحرِّكِها.
والدليلُ الأبسطُ على أنّ الفرقَ ليس اصطلاحًا: الفعلانِ يختلفانِ في النوعِ المُرجَع. مقيسٌ بالتشغيل:
متغير الجذر = عمود(نص_عنصر("مرحبًا")، زر("انقرني"))
متغير صفحة = توليد_ويب(الجذر، "تجربة")
اطبع_سطر("نوعُ الناتج: " + نوع(صفحة)) # ⇐ نصتوليد_ويب تُرجِعُ نصًّا تكتبُه أنتَ إلى ملفٍّ وترفعُه إلى خادمٍ أو تفتحُه في متصفّح. وتشغيل_تطبيق لا تُرجِعُ نصًّا؛ تفتحُ نافذة.
لماذا الخلطُ بينهما عطبٌ لا اختصار
«لغةُ ص تدعمُ خمسَ منصّاتٍ» جملةٌ صادقةُ العددِ كاذبةُ المعنى، لأنّها تُسوّي بينَ شيئَين لا يُسوَّى بينهما:
| ما يظنُّه القارئُ | ما يقعُ فعلًا في مولِّدِ المصدر |
|---|---|
| أبني تطبيقَ أندرويدَ من ص | أُخرِجُ ملفَّ .kt أفتحُه في Android Studio |
| المعالِجُ الذي كتبتُه يعمل | المولَّدُ يُخرِجُ onClick = {} — الجسدُ لا يُنقَل |
| أشغِّلُ ولا أغادرُ اللغة | أغادرُها عندَ أوّلِ بناء: Gradle أو Xcode |
والسطرُ الثاني مقيسٌ لا مُستنتَج: مخرَجُ توليد_أندرويد على زرٍّ مربوطٍ بمعالِجٍ في ص يحملُ onClick = {} فارغًا. فمولِّدُ المصدرِ ينقلُ البنيةَ والخصائص، لا منطقَ ص.
وليس هذا نقصًا في المولِّد: هو تعريفُه. المولِّدُ يُعطيكَ نقطةَ بدايةٍ بنيويّةً في منصّةٍ أخرى، وهذه قيمةٌ حقيقيّة — ما دامت تُسمّى باسمِها.
enabled: true ليس دعوى قدرة
ui_nodes.yaml يُعلِنُ ستَّ خلفيّاتِ تصيير: خمسٌ بـenabled: true وواحدةٌ بـfalse. وثلاثٌ من الخمسِ — الويبُ وأندرويدُ وiOS — لم يُقَسْ صدقُ إعلانِها.
والشاهدُ في الملفِّ نفسِه: خلفيّةُ ماك مُعلَنةٌ في السطورِ عينِها، enabled: false، وسببُها مكتوبٌ حرفيًّا — تستدعي طبقةَ تفاعلٍ (hitTest/fireEvent/focusedNode_) لا تصرّحُ بها ترويستُها فلا تُترجَمُ كما هي. فالملفُّ إذًا يصفُ حالَ الشفرةِ ووصفُه قابلٌ للانحراف، لا يُثبِتُ أنّها تُبنى.
القاعدة
لا تُشتَقُّ قدرةٌ من علَمِ تفعيل. القدرةُ تُشتَقُّ من status/intent في مصدرِ الحقيقة — وهي حقولٌ قِيسَ صدقُها — أو من بناءٍ يجري وتشغيلٍ يُرى. ولذلك بطاقةُ الهواتف خاليةُ الدعاوى مع أنّ في هذا الملفِّ ما يُغري بملئِها.
ما تحتَ العنصرِ دَينٌ مُعلَن
العنصرُ يُرسَم، لكنّ ما حولَه — النوافذُ والمدخلاتُ والملفّاتُ ودورةُ الحياة — طبقةٌ اسمُها UIPlatform، مُعلَنةٌ ٤٣ مدخلًا واثنانِ منها مستقرّان (ارسم_خط وارسم_مستطيل). والحوارُ والتخزينُ والصوتُ والمؤقِّتاتُ فضاءاتُ أسماءٍ صفرُ مستقرٍّ في كلٍّ منها.
فما تقرؤه ههنا سطحُ بناءِ شجرةٍ وتصييرِها، لا سطحَ تطبيقٍ مكتمِل.
ولا حارسَ للمخرَجِ المرئيّ
في tests/behavior بذورٌ تحرسُ نحوَ الواجهةِ وربطَها: مجلَّداتُ gr.adv.ui_decl وgr.adv.ui_event وgr.adv.ui_state و gr.adv.ui_modifier_chain وgr.adv.widget في rules_matrix، وستُّ بذورٍ في sections/12_تكامل.
ولا بذرةَ واحدة فيها كلِّها تستدعي تشغيل_تطبيق — مقيس. أي أنّ البكسلَ وحلقةَ الأحداثِ خارجَ كلِّ بوّابة. ونقطةُ الارتساءِ الوحيدةُ المتاحةُ اليومَ نصُّ طباعة_شجرة، وهي أصلحُ ما يُبنى عليه حارسٌ قادم.
لماذا يُكتَبُ هذا الحدُّ بدلَ أن يُطوى
لأنّ شاهدَ نضجٍ يُختلَقُ ليرفعَ الدرجةَ أسوأُ من درجةٍ منخفضة: الدرجةُ المنخفضةُ تُصلَحُ بعملٍ، والشاهدُ المختلَقُ يُفسِدُ كلَّ درجةٍ أخرى في المنظومة.
ما يُقرَأُ بعدَه
- جدولُ المخارجِ ونوعِ كلٍّ منها ⟵ المخارج
- بناءُ الشجرةِ عمليًّا ⟵ كيف تبني شجرةَ الواجهة
- الحدودُ المُعلَنةُ كاملةً ⟵ حدودُ SadUI