اذهب إلى المحتوى

التعرف على بعض أصناف مكتبة جافا إف إكس JavaFX البسيطة


رضوى العربي

سنُناقش خلال هذا القسم بعضًا من الأصناف البسيطة المُستخدَمة لتَمثيِل كُلًا من الألوان والخطوط والصور، كما سنرى طريقة اِستخدَامها مع كائن السياق الرسومي GraphicsContext الذي تَعرَّضنا له مبدئيًا بالقسم ٣.٩. إلى جانب ذلك، تستطيع أيضًا اِستخدَام تلك الأصناف بأجزاء آخرى من مكتبة جافا إف إكس (JavaFX). وأخيرًا، سنتناول مقدمة مُختصَرة عن أوراق الأنماط المُتعاقبة (CSS style sheet) والتي يُمكِنك أن تَستخدِمها للتَحكُّم بالكثير من الجوانب المرئية لمُكوِّنات واجهة المُستخدِم الرسومية (GUI).

الأصناف Color و Paint

يشيع اِستخدَام نظام الألوان RGB لتَخْصِيص الألوان عمومًا. يَتَألف كل لون وفقًا لهذا النظام من ثلاثة أعداد يُطلق عليها اسم "مُكوِّنات اللون (color components)" والتي تُعطِي درجات الأحمر والأخضر والأزرق باللون. بمكتبة جافا إف إكس (JavaFX)، يُستخدَم كائن من النوع Color المُعرَّف بحزمة javafx.scene.paint لتمثيل لون معين. كل مُكوِّن لون من المُكوِّنات الثلاثة هو قيمة من النوع double تَقَع بنطاق يتراوح بين ٠ و ١. تَملُك الكائنات من النوع Color مُكوِّنًا رابعًا أيضًا تتراوح قيمته بين ٠ و ١، ويُشار إليه باسم مكون اللون "ألفا (alpha)"، ويُستخدَم لتَمثيِل درجة شفافية (transparency) أو عتمة (opaqueness) اللون عند اِستخدَامه للرَسْم. إذا اِستخدَمت لونًا مُعتِمًا تمامًا (مُكوِّن اللون ألفا يُساوِي ١) للرَسْم، فسيَحلّ ذلك اللون مَحلّ اللون الحالي لسطح الرسم أما إذا اِستخدَمت لونًا شفافًا تمامًا (مُكوِّن اللون ألفا يُساوِي ٠)، فلن يَكُون له أي تأثير نهائيًا. إذا وَقَع مُكوِّن اللون ألفا بين ٠ و ١، يُخلَط لون الرسم مع اللون الحالي ليُعطِي لونًا جديدًا لسطح الرسم، ويبدو عندها المحتوي الأصلي لسطح الرسم كما لو كان مرئيًا من خلف نظارة مُلوَّنة شفافة. يُمكِنك إنشاء كائن من النوع Color بإعطاء قيمة مُكوِّنات اللون الأربعة (الأحمر والأخضر والأزرق وألفا) على النحو التالي:

Color myColor = new Color( r, g, b, a );

حيث r و g و b و a واقعة بنطاق يَتَراوح من ٠ إلى ١. يَملُك الصَنْف Color أيضًا عددًا من التوابع الساكنة (static methods) لإنشاء كائنات.

ملحوظة: يُطلق اسم "التوابع المُنتِجة (factory methods)" على التوابع الساكنة التي يَقْتصِر دورها على إنشاء كائنات. تستطيع إذًا أن تَستخدِم تابعًا منتجًا بدلًا من باني الكائن (constructor) كالتالي:

Color myColor = Color.color( r, g, b, a );

كما تستطيع اِستخدَام ما يلي إذا كنت تَرغَب بلون مُعتِم تمامًا، أي بقيمة a تُساوِي ١:

Color myColor = Color.color( r, g, b );

تُفضَّل التوابع المُنتِجة (factory methods) الساكنة على البواني (constructor) عمومًا لأنها تُمكِّنك من إعادة اِستخدَام نفس كائنات الألوان من الصَنْف Color. على سبيل المثال، إذا استدعيت التعبير Color.color(0.2,0.3,1.0)‎ أكثر من مرة، فسيُعاد دائمًا نفس الكائن من الصَنْف Color، وهو ما يُعدّ مناسبًا لأن كائنات الألوان غَيْر قابلة للتَعْدِيل (immutable) أي ليس هناك طريقة لتَغْيِير لون بَعْد إِنشائه، لذلك لا حاجة لاِستخدَام كائنات مختلفة لتمثيل نفس اللون.

عادةً ما تَستخدِم شاشات الحاسوب لونًا حجمه ٣٢ بت أي تُستخدَم مساحة قدرها ٨ بت لتمثيل كل مُكوِّن من مُكوِّنات اللون (color components) الأربعة، والتي يُمكِنها أن تُمثِل ٢٥٦ عدد صحيح بنطاق يتراوح من ٠ إلى ٢٥٥، ولهذا تُخصَّص ألوان الحاسوب باِستخدَام مُكوِّنات لون قيمتها تتراوح بين ٠ و ٢٥٥. يمتلك الصَنْف Color التابع الساكن (static method) التالي لإنشاء ألوان بتلك الطريقة:

Color.rgb( r, g, b )

حيث r و g و b هي أعداد صحيحة واقعة بنِطاق يَتَراوح من ٠ إلى ٢٥٥. يَتَوفَّر أيضًا Color.rgb(r,g,b,a)‎ حيث r و g و b هي أعداد صحيحة واقعة بنطاق يَتَراوح من ٠ إلى ٢٥٥ بينما a هي قيمة من النوع double تتراوح من ٠ إلى ١.

يَتَألف كل لون وفقًا لهذا النظام من ثلاثة أعداد يُطلق عليها اسم "مُكوِّنات اللون (color components)" والتي تُعطِي درجات الأحمر والأخضر والأزرق باللون.

بالإضافة إلى نظام الألوان RGB، يَشيِع أيضًا اِستخدَام نظام الألوان HSB. يَتَألف كل لون وفقًا لهذا النظام من ثلاثة أعداد تُحدِّد كُلًا من: درجة اللون (hue) والإشباع (saturation) والسطوع (brightness). درجة اللون (hue) هي اللون الأساسي بدايةً من الأحمر مرورًا بالبرتقالي وغيرها من ألوان الطيف الأخرى أما السطوع (brightness) فمعناها مفهوم ضمنيًا. بالنسبة للإشباع (saturation)، فاللون المُشبع (saturated) تمامًا هو لون نقي، ويُمكِننا تَقْليِل درجة إشباع لون معين بخلطه مع لون أبيض أو رمادي. بمكتبة جافا إف إكس (JavaFX)، تُعطَى درجة اللون (hue) كقيمة من النوع double تَتَراوح من ٠ إلى ٣٦٠ أما الإشباع (saturation) والسطوع (brightness) فهي قيم من النوع double تتراوح من ٠ إلى ١. تُخصَّص قيمة درجة اللون بوحدة الدرجة (degrees) كما لو كانت جميع الألوان مُمدَّدة عبر دائرة بحيث تُمثِل الدرجتان ٠ و ٣٦٠ لونًا أحمرًا نقيًا. يُعرِّف الصَنْف Color التابعين الساكنين Color.hsb(h,s,b)‎ و Color.hsb(h,s,b,a)‎ لإنشاء ألوان بنظام HSB. على سبيل المثال، يُمكِنك اِستخدَام الشيفرة التالية لإنشاء لون مُشِع (bright) ومُشبِع (saturated) بأقصى ما يُمكِن، وبدرجة لون (hue) عشوائية:

Color randomColor = Color.hsb( 360*Math.random(), 1.0, 1.0 );

يُعدّ نظامي اللون RGB و HSB طريقتين مختلفين لوصف نفس مجموعة الألوان، ويُمكِننا في الواقع أن نُحوِّل بينهما. الطريقة الأفضل عمومًا لفهم أنظمة الألوان هو تجريبها، وهو ما تستطيع القيام به باِستخدَام المثال التوضيحي SimpleColorChooser.java. لن تَفهَم شيفرة البرنامج بالكامل، ولكن يُمكِنك أن تُشغِّله وتُجرِّبه.

يحتوي الصنف Color على عدد كبير من الثوابت (constants) الممثلة للألوان مثل Color.RED و Color.BLACK و Color.LIGHTGRAY و Color.GOLDENROD. لاحظ أن اللون Color.GREEN يمثل اللون الأخضر الداكن المعطى باستخدام Color.rgb(0,128,0)‎ أما ذلك المعطى باستخدام Color.rgb(0,255,0)‎ فيُمثِل Color.LIME. يتوفر أيضًا اللون Color.TRANSPARENT والذي يمثل لونًا شفافًا تمامًا حيث جميع مكونات اللون RGBA الأربعة تُساوي صفر.

بفَرْض وجود مُتْغيِّر c يُشير إلى كائن من النوع Color، يُمكِنك استدعاء دوال مثل c.getRed()‎ و c.getHue()‎ و c.getOpacity()‎ لجَلْب قيم مُكوِّنات اللون المختلفة. تُعيد تلك التوابع (methods) قيمًا من النوع double بنطاق يَتَراوح من ٠ إلى ١ باستثناء التابع c.getHue()‎ والذي يُعيد قيمًا من النوع double تَقَع بنطاق يَتَراوح من ٠ إلى ٣٦٠.

لاحِظ أن Color هو صَنْف فرعي (subclass) من الصَنْف Paint والذي يُمثِل الفكرة الأعم لشيء يُمكِن اِستخدَامه لمَلْئ (fill) الأشكال ورَسْم حوافها (stroke). إلى جانب الألوان، يُمكِنك أيضًا اِستخدَام طلاءً مُكوَّنًا من صور (image paints) أو طلاءً مُتدرجًا (gradient paints). سنُعود للحديث عن ذلك بالقسم الفرعي ١٣.٢.٢ أما الآن فينبغي أن تَعلَم فقط أنه بإِمكانك أن تَستخدِم كائنًا من النوع Color كقيمة لمُعامل (parameter) من النوع Paint.

الخطوط Font

يُعبِر الخط (font) عن كُلًا من هيئة النص وحجمه، فيختلف مَظهَر المحرف (character) ذاته بتَغَيُّر الخط. تُستخدَم كائنات من النوع Font المُعرَّف بحزمة javafx.scene.text لتمثيل الخطوط بمنصة جافا إف إكس (JavaFX). يُوفِّر الصَنْف Font عددًا من البواني (constructors) وكذلك بعضًا من التوابع المُنتِجة (factory methods) الساكنة لإنشاء كائن خط من الصَنْف Font، ويُفضَّل عمومًا استخدام التوابع المُنتِجة عن التوابع.

كل خط له اسم عبارة عن سِلسِلة نصية تُخصِّص نوع الخط (font family) مثل "Times New Roman". عادة ما تَتَوفَّر نسخ مختلفة لنفس نوع الخط، مثلًا نُسخة عريضة (bold) وآخرى مائلة (italic). أخيرًا، يُقاس حجم الخط (size) بوحدة النقاط (points)، والتي تُعادِل حوالي ١\٧٢ بوصة، وتُساوِي عمليًا حجم بكسل واحد. يُمكِنك اِستخدَام الدالة (function) التالية لإنشاء خط:

Font myFont = Font.font( family, weight, posture, size );

إذا لَمْ يَجِد النظام خطًا تَتَطابَق خاصياته مع قيم المُعامِلات (parameters) المُمرَّرة تطابقًا كليًا، فإنه يُعيِد خطًا أقرب ما يَكُون إليها. أولًا، المُعامِل family هو عبارة عن سِلسِلة نصية من الصنف String تُخصِّص إحدى أنواع الخطوط المتاحة للبرنامج. في الواقع، ليس هنالك مجموعة خطوط لابُدّ أن تَكُون متاحة في العموم، ومع ذلك من المُتوقَّع دومًا أن تَعمَل خطوط مثل "Times New Roman" و "Arial" و "Verdana"، والتي صنعتها مايكروسوفت (Microsoft) ونشرتها للاستخدام الحر، وهي مُثبَّتة بالكثير من الأنظمة. يُمكِنك أن تُمرِّر null كقيمة لهذا المُعامِل، وسيَستخدِم النظام عندها نوع الخط الافتراضي.

ثانيًا: يَستقبِل المُعامِل weight إحدى قيم نوع التعداد FontWeight لتَخْصِيص وزن الخط. عادة ما تَكُون القيمة المُمرَّرة هي FontWeight.BOLD أو FontWeight.NORMAL على الرغم من وجود عدة قيم آخرى مثل FontWeight.EXTRA_BOLD. بالمثل، يُمرَّر للمُعامِل posture أي من الثابتين FontPosture.ITALIC و FontPosture.REGULAR لتَخْصيص وَضْع الخط. لاحِظ أن التعدادين FontWeight و FontPosture مُعرَّفان بحزمة javafx.scene.text.

يَتَضمَّن الصَنْف Font بعض الدوال (functions) الساكنة الآخرى المُستخدَمة لإنشاء خطوط، وتَستقبِل كل واحدة منها مجموعة معينة من الخاصيات الأربعة: family و weight و posture و size مثل الدوال Font.font(size)‎ و Font.font(family)‎ و Font.font(family,weight,size)‎ وغيرها. تَتَوفَّر قيم افتراضية للخاصيات غَيْر المُمرَّرة تعتمد عادةً على الحاسوب المُشغَّل عليه البرنامج. تُعيد الدالة الساكنة Font.getDefault()‎ خطًا تَستخدِم جميع خاصياته قيمها الافتراضية، ويُمكِنك مثلًا أن تَستدعِي Font.getDefault().getSize()‎ لمَعرِفة الحجم الافتراضي للنقطة (point). اُنظر الأمثلة التالية لإنشاء خطوط:

Font font1 = Font.font(40);
Font font2 = Font.font("Times New Roman", FontWeight.BOLD, 24);
Font font3 = Font.font(null, FontWeight.BOLD, FontPosture.ITALIC, 14);

الصور Image

تُوفِّر منصة جافا إف إكس (JavaFX) الصَنْف Image بحزمة javafx.scene.image لتمثيل "الصور (images)"، والتي قد تَكُون بهيئة صورة فوتوغرافية أو رسم أو أي شيء آخر يُمكِن تمثيله بهيئة شبكة مستطيلة من البكسلات الملونة. تُخزَّن تلك الصور بملفات يُمكِن تحميلها بسهولة تمهيدًا لعَرْضها ضِمْن البرنامج. يُكْتَب الباني (constructor) كالتالي:

new Image( path )

يُحمِّل الباني بالأعلى صورة من ملف. يَستقبِل ذلك الباني سِلسِلة نصية (string) كقيمة للمُعامِل path تُخصِّص موقع (location) الملف. قد يَقَع الملف بشبكة الإنترنت أو بجهاز المُستخدِم، وسنُركِّز هنا فقط على تلكم الواقعة ضِمْن ملفات الموارد (resource). تُسمَى أجزاء البرنامج من غير ملفات الشيفرة باسم الموارد (resource)، والتي قد تَتَضمَّن أنواع مختلفة من الملفات كملفات الصوت والبيانات والخطوط والصور. بإمكان النظام أن يُحمِّل تلك الموارد (resources) من نفس المسارات التي يبحث فيها عن ملفات ‎.class. مثلًا، إذا كان لدينا ملف مورد (resource file) بمجلد البرنامج الرئيسي، فإن مساره يَقْتصِر على اسمه أما إذا كان الملف موجودًا بمجلد فرعي داخل المجلد الرئيسي، فلابُدّ أن يَتَضمَّن مساره اسم ذلك المجلد الفرعي. مثلًا، يُشير المسار "images/cards.png" إلى ملف اسمه "cards.png" بمجلد فرعي اسمه "images"، أما المسار "resources/sounds/beep.aiff" فيُشير إلى ملف اسمه "beep.aiff" بمجلد اسمه "sounds" والذي بدوره يَقَع بمجلد اسمه "resources".

تَتَوفَّر صيغ (formats) مختلفة لتَخْزين الصور، وتستطيع الكائنات من الصَنْف Image أن تتعامل في العموم مع ملفات الصور التي تنتهي أسمائها بإحدى الامتدادات التالية: ‎.gif و ‎.jpeg و ‎.jpg و ‎.png و ‎.bmp. فمثلًا، إذا كان الملف "cards.png" واقعًا بمجلد البرنامج الرئيسي، يُمكِننا أن نُنشِئ كائنًا من الصنف Image لتمثيله كالتالي:

Image cards = new Image( "cards.png" );

سنرى بعد قليل طريقة عَرْض الصورة ضِمْن سياق رسومي (من الصنف GraphicsContext)، كما سنناقش بعض الاستخدامات الأخرى للصور لاحقًا.

الحاوية Canvas والسياق الرسومي GraphicsContext

تَتَكوَّن أي شاشة حاسوب من شبكة من المربعات الصغيرة يُطلَق على كُلًا منها اسم "بكسل (pixel)". نستطيع أن نَضبُط لون كل بكسل منها على حدى، وبالتالي يُمكِننا أن نَرسِم أي شيء على الشاشة مثل أي من مُكوِّنات الواجهة (GUI component). تستطيع غالبية مُكوِّنات الواجهة أن تَرسِم نفسها كما تَملُك نظامًا إحداثيًا لتَحْوِيل الإحداثيات (x,y) إلى نقط واقعة عليها. بالإضافة إلى ذلك، يتوفَّر مُكوِّن وحيد عبارة عن "مساحة رسم (drawing surface)" تستطيع أن تَرسِم عليه أي شيء فقط باستدعاء بعض التوابع (methods) المناسبة. مُكوِّنات مساحة الرسم تلك هي من نوع الحاوية Canvas المُعرَّف بحزمة javafx.scene.canvas، والتي تُعدّ عُقدًا من الصَنْف Node أي يُمكِنها أن تَكُون جُزءًا من مبيان مَشهَد (scene graph)، ولكنها ليست من الصَنْف Parent لذا لا يُمكِنها أن تَعمَل كحاوي (container) لعُقَد آخرى كما لا يُمكِنها أن تَكُون المُكوِّن الجذري (root) لمبيان مَشهَد. يَعنِي ذلك أنه حتى لو رغبت برسم كائن حاوية (canvas) فقط على نافذة، فلابُدّ من أن تَضَعُه أولًا ضِمْن حاوي (container) يَعمَل كمُكوِّن جذري لمبيان مشهد (scene graph) تلك النافذة.

تبدو الحاوية من الصَنْف Canvas بهيئة مستطيل من البكسلات (pixels) على الشاشة. يُستخدَم زوج من الإحداثيات (x,y) للإشارة إلى مَوضِع معين ضِمْن ذلك المستطيل، والذي يُمثِل رُكنه الأيسر العلوي إحداثيات النقطة (٠،٠)، وبحيث تزداد قيمة الإحداثي x من اليسار إلى اليمين بينما تزداد قيمة الإحداثي y من الأعلى إلى الأسفل. تُوضِح الصورة التالية الكيفية التي تُلوَّن بها البكسلات لرَسْم شكل معين مثل خط صغير أو مستطيل أو شكل بيضاوي على حاوية (canvas) بمساحة ١٢ * ١٢ بكسل.

001Pixel_Coordinates.png

لا تَقَع إحداثيات النقط داخل البكسلات ذاتها وإنما على خطوط الشبكة (grid) الفاصلة بينها، ولمّا كانت تلك الإحداثيات أعدادً من النوع double، فإنها قد تُضبَط لكي تُشيِر إلى موضع داخل بكسل. على سبيل المثال، تُمثِل الإحداثيات (٠.٥، ٠.٥) مركز البكسل الأيسر العلوي بحاوية (canvas) معينة. ملحوظة: تُرسَم كل الأشكال عمومًا باستخدام إحداثيات من النوع double.

يُوفِّر الصَنْف Canvas باني كائن (constructor) يَستقبِل كُلًا من عرض الحاوية (canvas) وارتفاعها. يُمكِننا مثلًا إنشاء حاوية صغيرة جدًا بحجم ٢٠ * ١٢ بكسل كالتالي:

Canvas canvas = new Canvas(20,12)

نستطيع مَعرِفة حجم حاوية (canvas) معينة باستدعاء التابعين canvas.getWidth()‎ و canvas.getHeight()‎ ويُعيد كُلًا منهما قيمة من النوع double. لا نحتاج عادةً إلى إعادة ضَبْط حجم حاوية بَعْد إنشائها، ومع ذلك يُوفِّر الصَنْف التابعين canvas.setWidth(w)‎ و canvas.setHeight(h)‎ لإعادة ضَبْط حجم حاوية إن اقتضت الضرورة.

لاحِظ أن الحاوية (canvas) تَكُون شفافة (transparent) بمُجرّد إنشائها، وذلك لأنها تُملأ افتراضيًا بلون أسود شفاف (transparent black) جميع مُكوِّناته RGBA مُساوية للصفر، ولهذا يُمكِن رؤية ما هو موجود خلفها ضِمْن المشهد (scene).

الآن لكي نَتَمكَّّن من الرسم على أي حاوية (canvas)، سنحتاج إلى كائن سياق رسومي (graphics context) من النوع GraphicsContext. يُقابِل أي كائن حاوية من الصَنْف Canvas كائن سياق رسومي من الصَنْف GraphicsContext خاص به. بتعبير آخر، يَرسِم كل كائن سياق رسومي مختلف من الصَنْف GraphicsContexts على كائن حاوية مختلف من الصَنْف Canvas. بفَرْض أن لدينا مُتْغيِّر كائن حاوية اسمه canvas من الصنف Canvas، يُمكِننا استدعاء التابع canvas.getGraphicsContext2D()‎ لمَعرِفة كائن السياق الرسومي (graphics context) للحاوية. سيُعيد ذلك التابع (method) نفس كائن السياق الرسومي دومًا لنفس كائن الحاوية. خلال القسم ٣.٩، تَعامَلنا مع كائن سياق رسومي (graphics context)، وتَعلَّمنا عمومًا كيفية رَسْم حواف (stroke) شكل معين أو ملؤه (fill) في حالة كان لديه جزءًا داخليًا. نَستعرِض الآن التوابع التي يُمكِننا اِستخدَامها بكائن سياق رسومي g من الصَنْف GraphicsContext مع مراعاة كَوْن جميع المُعامِلات (parameters) العَدَدية لتلك التوابع من النوع double:

  • التابعان g.strokeRect(x,y,w,h)‎ و g.fillRect(x,y,w,h)‎: يَرسِم كلاهما مستطيلًا بعَرْض w وارتفاع h وبحيث يَقَع ركنه الأيسر العلوي بإحداثيات النقطة (x,y). لن يُرسَم أي شيء إذا كانت قيمة أي من المُعامِلين w أو h أقل من أو تُساوِي الصفر.
  • التابع g.clearRect(x,y,w,h)‎: يَملأ المستطيل بلون شفاف (transparent) تمامًا، وبالتالي يُمكن رؤية ما كان موجودًا خلفه عبر الحاوية (canvas)، وهو ما يختلف عن استدعاء التابع g.fillRect(x,y,w,h)‎ باِستخدَام لون مَلْئ (fill color) شفاف، ففي تلك الحالة، لا يَكُون هناك أي تأثير على محتويات المستطيل.
  • التابعين g.strokeOval(x,y,w,h)‎ و g.fillOval(x,y,w,h)‎: يَرسِم كلاهما شكلًا بيضاويًا يَقَع داخل مستطيل عَرْضه هو w وارتفاعه هو h وبحيث يَقَع ركنه الأيسر العلوي بإحداثيات النقطة (x,y).
  • التابعان g.strokeRoundRect(x,y,w,h,rh,rv)‎ و g.fillRoundRect(x,y,w,h,rh,rv)‎: يَرسِم كلاهما مستطيلًا دائري الأركان بعَرْض w وارتفاع h وبحيث يَقَع ركنه الأيسر العلوي بإحداثيات النقطة (x,y). يُقطَّع من كل ركن ربع بيضاوي نصفي قطره الأفقي والرأسي مُساوي للقيم rh و rv على الترتيب.
  • التابعان g.strokeText(str,x,y)‎ و g.fillText(str,x,y)‎: يَرسِم كلاهما سِلسِلة نصية من النوع String وبحيث يَقَع الطرف الأيسر من خط النص الأساسي (text baseline) بنقطة الإحداثيات (x,y). تُرسَم أي سِلسِلة نصية (string) عمومًا فوق خط أساسي (baseline) مع إمكانية لتدلّي بعض الأجزاء كذيل الحرف "y" الذي يَمتد إلى ما بَعْد الخط الأساسي بقليل. إذا اِمتدَّت سِلسِلة نصية معينة عَبْر عدة أسطر مفصولة بمحرف سطر جديد ‎\n، ستُشير عندها نقطة الإحداثيات (x,y) إلى طرف الخط الأساسي (baseline) الأيسر لأول سطر بالسِلسِلة النصية. لاحِظ أن رَسْم حواف (stroking) نص معين يعني مُجرّد رَسْم حواف محارفه (characters) الخارجية.
  • التابعان g.strokePolygon(xcoords,ycoords,n)‎ و g.fillPolygon(xcoords,ycoords,n)‎: يَرسِم كلاهما مُضلعًا (polygon) مُكوَّنًا من عدة خطوط تَربُط مجموعة من النقط بعضها ببعض، والتي تُحدَّد إحداثياتها بالقيم المُمرَّرة للمُعامِلين الأول والثاني، وتَكُون بهيئة مصفوفة من النوع double[]‎. يَستقبِل التابعان أيضًا مُعامِلًا ثالثًا n يُشير إلى عدد تلك النقاط. يَصِل المُضلع النقط التالية مع بعضها البعض: (xcoords[0],ycoords[0]‎) و (xcoords[1],ycoords[1]‎) و … و (xcoords[n-1],ycoords[n-1]‎) و (xcoords[0],ycoords[0]‎) أي يَصِل تلقائيًا خط إضافي أخير بين النقطتين الأولى والأخيرة.
  • التابع g.strokeLine(x1,y1,x2,y2)‎: يَرسِم خطًا يبدأ من إِحداثيَّات نقطة البداية (x1,y1) وحتى إِحداثيَّات نقطة النهاية (x2,y2). ولأن الخط لا يَملُك جزءًا داخليًا، فليس هنالك أي مَعنَى لمحاولة ملؤه (fill).

يَملُك أي كائن سياق رُسومي g من الصَنْف GraphicsContext عددًا من الخاصيات التي تُؤثِر على عملية الرَسْم حيث تُستخدَم القيم الحالية لخاصياته عند اِستخدَامه لرَسْم أي شيء. يُناظِر كل خاصية منها تابعي ضَبْط (setter method) وجَلْب (getter method)، ولكن لاحِظ أن أي تَغْيِير يُجرَى على أي من تلك الخاصيات لن يُؤثِر على ما قد رُسِم بالفعل وإنما يُطبَق فقط على ما سيُرسَم مستقبلًا. على سبيل المثال، تُعدّ الطريقة المُستخدَمة للمَلْئ (filling) واحدة من تلك الخاصيات ويُسنَد إليها كائن من الصَنْف Paint. يَتَوفَّر التابعان g.setFill(paint)‎ و g.getFill()‎ لضَبْط قيمة تلك الخاصية وجَلْبها على الترتيب. سنَستخدِم كائنًا من الصَنْف Color لهذه الخاصية دائمًا خلال هذا الفصل. الطريقة المُستخدمَة لرَسْم حواف الأشكال (stroking) هي خاصية آخرى بكائن السياق الرسومي ويُسنَد إليها أيضًا كائن من الصَنْف Paint يُمكِن ضَبْطه وجَلْبه باِستخدَام التابعين g.setStroke(paint)‎ و g.getStroke()‎ على الترتيب. يُمكِن أيضًا ضَبْط خاصية حجم الحواف (strokes) وجَلْب قيمتها باستدعاء التابعين g.setLineWidth(w)‎ و g.getLineWidth()‎ على الترتيب حيث w قيمة من النوع double. يَتَوفَّر أيضًا التابعان g.setFont(font)‎ و g.getFont()‎ لضَبْط قيمة خاصية الخط (font) المُستخدَم لرسم النصوص وجَلْب قيمتها على الترتيب.

إن عملية رَسْم حواف شكل معين (stroking) هي أَشْبه ما تَكُون بعملية سَحب قلم على طول حواف ذلك الشكل، وبحيث تُرسَم تلك الحواف على جانبي المسار الفعلي للقلم بعَرْض يُساوِي نصف عَرْض (width/size) القلم على كل جانب. يُحدَّد عَرْض القلم باِستخدَام الخاصية linewidth. على سبيل المثال، إذا رسمنا خطًا أفقيًا بين النقطتين (١٠٠ ،١٠٠) و (٣٠٠، ١٠٠) بعَرْض يُساوِي ١، فسيَقَع نصف الحافة (stroke) أعلى الخط الأفقي بينما سيقع النصف الآخر أسفله، لذلك لا يُغطَى البكسل بالكامل ويضطرّ الحاسوب عندها لخَلْط كُلًا من اللون المُستخدَم لرسم الحافة واللون الحالي للبكسل. في المقابل، إذا رسمت خطًا بين النقطتين (١٠٠.٥، ١٠٠.٥) و (٣٠٠.٥، ١٠٠.٥)، فسيُغطَي البكسل بالكامل. في العموم، عندما تَرسِم شيئًا يُغطِي بكسلًا معينًا تغطية جزئية، فإن اللون المُستخدَم للرسم يُمزَج مع اللون الحالي للبكسل بدلًا من أن يستبدله، وذلك لتقليل خشونة الأشكال المُكوَّنة من بكسلات كاملة مثل الخط والشكل البيضاوي بالصورة السابقة، وهو ما يُعرَف بإجراء "التنعيم (anti-aliasing)".

يُمكِننا أيضًا أن نرسم كائن صورة من النوع Image داخل حاوية (canvas)، ونَستعرِض فيما يلي بعضًا من التوابع (methods) المُستخدَمة لهذا الغرض:

  • التابع g.drawImage(image,x,y)‎: يَستقبِل صورة من خلال المُعامِل image من النوع Image، ويَرسمها بحيث يَقَع ركنها الأيسر العلوي بإحداثيات النقطة (x,y)، وتَظَهَر الصورة بحجمها الفعليّ.
  • التابع g.drawImage(image,x,y,w,h)‎: يَرسِم الصورة المُمثَلة بالمُعامِل image داخل مستطيل يَقَع ركنه الأيسر العلوي بإحداثيات النقطة (x,y) بعَرْض يُساوِي w وارتفاع يُساوِي h. قد تَمتدّ الصورة أو تنكمش بحيث تتلائم مع المستطيل الحاضن لها.
  • التابع g.drawImage(image,sx,sy,sw,sh, dx,dy,dh,dw)‎: يَرسِم المحتويات الموجودة "بمستطيل مصدر (source)" مُحدَّد ضِمْن الصورة المُمرَّرة image إلى "مستطيل مقصد (destination)" مُحدَّد ضِمْن الحاوية (canvas) مما يَسمَح برسم جزء معين من صورة. يُحدِّد المُعامِلان sx و sy إحداثيات النقطة التي يَقَع بها الركن الأيسر العلوي "لمستطيل المصدر" بينما يُحدِّد المُعامِلان sw و sh كُلًا من عَرْض المستطيل وارتفاعه. بالمثل، تُحدِّد المُعامِلات (parameters) الأربعة الأخيرة المُمرَّرة مَوضِع "مستطيل المقصد" وأبعاده.

لنأخُذ بعض الأمثلة الفعلية. يَستخدِم المثال التالي تشكيلة مختلفة من الخطوط (fonts) لرَسْم نص (text). تحديدًا، يَرسِم البرنامج السِلسِلة النصية "Hello JavaFX" بخطوط مختلفة كما يَملْئ (fill) النص بلون عشوائي مع حواف (stroke) سوداء نحيفة، وأخيرًا يضعها بمكان عشوائي.

002Random_Strings.png

يَستخدِم التابع start()‎ بعض التوابع المُنتجة (factory methods) الساكنة المُعرَّفة بالصنف Font لإنشاء خمسة خطوط (fonts):

font1 = Font.font("Times New Roman", FontWeight.BOLD, 20);
font2 = Font.font("Arial", FontWeight.BOLD, FontPosture.ITALIC, 28);
font3 = Font.font("Verdana", 32);
font4 = Font.font(40);
font5 = Font.font("Times New Roman",FontWeight.BOLD,FontPosture.ITALIC,60);

يُعرِّف البرنامج تابعًا اسمه draw()‎ ويُوكِل إليه مُهِمّة إعادة رَسْم محتويات حاوية (canvas). يُستدعَى ذلك التابع عند إنشاء حاوية (canvas) لأول مرة، وكذلك بكل مرة يَنقُر فيها المُستخدِم على زر "أَعِد الرسم (redraw)". يَملأ التابع أولًا تلك الحاوية (canvas) بخلفية بيضاء لمسح محتوياتها السابقة، ثم يَرسِم ٢٥ نسخة من السِلسِلة النصية "Hello JavaFX" بأماكن عشوائية ضِمْن الحاوية، وبحيث تَكُون كل نُسخة منها مملوءة بلون (fill color) عشوائي ومُحاطة بحواف سوداء وكذلك مرسومة بخط (font) عشوائي:

private void draw() {

    GraphicsContext g = canvas.getGraphicsContext2D();

    double width = canvas.getWidth();
    double height = canvas.getHeight();

    g.setFill( Color.WHITE );  // fill with white background
    g.fillRect(0, 0, width, height);

    for (int i = 0; i < 25; i++) {

        // اضبط الخط لأي من الخطوط الخمسة المتاحة

        int fontNum = (int)(5*Math.random()) + 1;
        switch (fontNum) {
        case 1:
            g.setFont(font1);
            break;
        case 2:
            g.setFont(font2);
            break;
        case 3:
            g.setFont(font3);
            break;
        case 4:
            g.setFont(font4);
            break;
        case 5:
            g.setFont(font5);
            break;
        } // end switch

        // اضبط اللون إلى درجة لون عشوائية ساطعة ومشبعة

        double hue = 360*Math.random();
        g.setFill( Color.hsb(hue, 1.0, 1.0) );

        // اضبط مَوضِع السلسلة النصية عشوائيًا

        double x,y;
        x = -50 + Math.random()*(width+40);
        y = Math.random()*(height+20);

        // ارسم الرسالة النصية

        g.fillText("Hello JavaFX",x,y);

        // ارسم حواف النص باللون الأسود

        g.setStroke(Color.BLACK);
        g.strokeText("Hello JavaFX",x,y);

    } // end for

} // end draw()

تَتَوفَّر شيفرة البرنامج كاملة بالملف RandomStrings.java.

يَرسِم البرنامج التالي ٥ ورق لعب سُحبَت عشوائيًا من مجموعة ورق لعب (deck) كالتالي:

003Random_Cards.png

اِستخدَمنا الصَنْفين Card و Deck من القسم ٥.٤ لتمثيل كُلًا من ورقة اللعب ومجموعة ورق اللعب على الترتيب. أُخذَت صور ورق اللعب بالأعلى من ملف المورد cards.png الموجود بالبرنامج، والمأخوذ من مشروع جينوم لسطح المكتب. يَتَضمَّن ذلك الملف صورًا لجميع ورق اللعب مُرتَّبة ضِمْن عدد من الصفوف والأعمدة. تَعرِض الصورة التالية نُسخة مُصغرة من ذلك الملف:

004Cards.png

يُمكِننا تحميل ملف الصورة إلى البرنامج بكتابة التالي داخل التابع start()‎:

cardImages = new Image("cards.png");

حيث cardImages هو مُتْغيِّر نُسخة (instance variable) من النوع Image. لنَفْترِض الآن أننا نريد رَسْم ورقة اللعب الواقعة برقمي الصف والعمود R و C على الترتيب ضِمْن كائن سياق رُسومي g من النوع GraphicsContext مع مراعاة أن الصفوف والأعمدة مُرقَّّمة بدءًا من الصفر. لمّا كان حجم أي ورقة لعب بالصورة هو ٧٩ * ١٢٣ بكسل، فإن الركن الأيسر العلوي لأي ورقة لعب يقع بإحداثيات النقطة (‎79*C,123*R) . إذًا، لكي نَضَع ورقة لعب داخل حاوية (canvas) بحيث يقع ركنها الأيسر العلوي بإحداثيات النقطة (x,y)، يُمكِننا أن نَستخدِم النسخة الثالثة من التابع drawImage()‎، والتي تَستقبِل "مستطيل مصدر" ضِمْن صورة بالإضافة إلى "مستطيل مقصد" ضِمْن حاوية (canvas) كالتالي:

g.drawImage( cardImages, 79*C,123*R,79,123, x,y,79,123 );

بفَرْض أن المُتْغيِّر card -من النوع Card- يُمثِل ورقة اللعب التي نَرغَب برسمها، يُمكِننا إذًا أن نَستخدِم التابعين card.getValue()‎ و card.getSuit()‎ لمَعرِفة كُلًا من قيمة (value) ورقة اللعب ورمزها (suit) على الترتيب. بعد ذلك، سنحتاج إلى تَحْوِيل هاتين القيمتين بطريقة ما إلى رقمي الصف والعمود المُناظرين لتلك الورقة. لمّا كنا ننوي ترك مسافة قدرها ٣٠ بكسل بين كل ورقة لعب والورقة التي تليها بالحاوية (canvas)، فلابُدّ من مراعاة ذلك عند حِسَاب مَوضِع ورقة اللعب. بالشيفرة التالية، يَسحَب التابع draw()‎ خمسة أوراق من مجموعة ورقة اللعب (deck) سحبًا عشوائيًا ثم يَرسِمها:

private void draw() {

    GraphicsContext g = canvas.getGraphicsContext2D();

    Deck deck = new Deck();
    deck.shuffle();

    // الركن الأيسر العلوي لمستطيل المصدر بصورة ورق اللعب
    double sx,sy;  
    // الركن الأيسر العلوي لمستطيل المقصد بالحاوية
    double dx,dy;  

    for (int i = 0; i < 5; i++) {
        Card card = deck.dealCard();
        System.out.println(card); // for testing
        sx = 79 * (card.getValue()-1);
        sy = 123 * (3 - card.getSuit());
        dx = 20 + (79+20) * i;
        dy = 20;
        g.drawImage( cardImages, sx,sy,79,123, dx,dy,79,123 );
    }

} // end draw()

يُمكِنك الإطلاع على النسخة الكاملة من البرنامج بالملف RandomCards.java.

التنسيق عبر CSS

تُعدّ لغة أوراق الأنماط المتعاقبة (Cascading Style Sheets) -وتُختصَر إلى CSS-، واحدة من ضِمْن عدة لغات يَشيِع اِستخدَامها لإنشاء صفحات الإنترنت، فبإمكانها أن تَتَحكَّم بالألوان والخطوط وكذلك بحواف العناصر ضِمْن الصفحة. تُدعِّم منصة جافا إف إكس (JavaFX) تلك اللغة بغرض ضَبْط مظهر مُكوِّنات واجهة المُستخدِم الرسومية (GUI). في الواقع، يُمكِن للغة الجافا أن تُنفِّذ أي شيء قد تقوم به تلك اللغة، ولكن هنالك بعض الأمور التي يَكُون من الأسهل تّنْفيذها باِستخدَام لغة CSS، وهو ما سنَتعرَض لبعض منه خلال هذا القسم. إذا كنت على دراية بلغة CSS، يُمكِنك الإطلاع على دليل استخدامها بجافا إف إكس.

تَتَكوَّن أي قاعدة نمط (style rule) بلغة CSS من خاصية (property) وقيمة، وتبدأ أسماء جميع تلك الخاصيات بمنصة جافا إف إكس (JavaFX) بـالمحارف "-fx-" لتفريقها عن خاصيات CSS العادية. على سبيل المثال، تَتَوفَّر الخاصيات ‎-fx-border-color و ‎-fx-border-width لوَضْع حافة (border) حول كثير من مُكوِّنات الواجهة. يُمكِنك أن تَستخدِم اسم لون مثل red أو lightblue كقيمة للخاصية ‎-fx-border-color أو قد تختار أن تَستخدِم الصياغة ‎#RRGGBB حيث R و G و B هي عبارة عن أرقام ست عشريّة (hexadecimal digits). يُمكِن للأعداد الست عشريّة (hexadecimal number) المُكوَّنة من رقمين (digit) أن تُمثِل الأعداد من ٠ وحتى ٢٥٥، لذا تستطيع RR و GG و BB تمثيل كُلًا من المُكوِّن الأحمر والأخضر والأزرق للون على الترتيب، وتتراوح قيم جميعها من ٠ وحتى ٢٥٥. على سبيل المثال، يُمثِل ‎#FF0000 لونًا أحمرًا نقيًا بينما يُمثِل ‎#004444 لونًا أخضر-أزرق داكنًا.

أما بالنسبة لخاصية عَرْض الحافة (border width)، فيُمكِنها أن تُخصَّص بقيمة واحدة مُفردة مُكوَّنة من عدد متبوع بوحدة قياس وبدون أي فراغ بينهما، وفي تلك الحالة، تُطبَق تلك القيمة على جميع جوانب الحافة الأربعة. على سبيل المثال، قد تُستخدَم القيمة "3px" أو "0.2cm". تُشير "px" إلى "بكسل (pixel)" وبالتالي "3px" تَعنِي ثلاثة بكسل. في المقابل، يُمكِن لقيمة الخاصية أن تُخصَّص بأربع قيم يَفصِل بين كُلًا منها فراغ، وذلك لتَحْديد عَرْض كل جانب من جوانب الحافة على حدى وفقًا لهذا الترتيب: أعلى، يمين، أسفل، يسار. يُمكِننا مثلًا تَخْصِيص حافة زرقاء سميكة كالتالي:

-fx-border-color: blue; -fx-border-width: 5px

أو يُمكِننا اِستخدَام التالي للحصول على حافة حمراء داكنة أكثر سماكة من الأعلى الموازنة مع الجوانب الآخرى:

-fx-border-color: #550000; -fx-border-width: 3px 1px 1px 1px

عندما تُستخدَم عدة قواعد CSS معًا، لابُدّ أن يَفصِل بينها فاصلة منقوطة.

يُمكِننا أيضًا أن نَضبُط لون الخلفية لمُكوِّن واجهة باِستخدَام الخاصية ‎-fx-background-color، وتُخصَّص قيمتها بنفس طريقة تَخْصِيص اللون للخاصية ‎-fx-border-color.

في المقابل، يُشار إلى الفراغ المتروك بين محتويات المُكوِّن وحافته الخارجية باسم "الحشوة (padding)"، وتُستخدَم الخاصية ‎-fx-padding لتَحْدِيد عَرْضها. بالمثل من خاصية عَرْض الحافة (border width)، تُخصَّص قيمة ‎-fx-padding إما كقيمة مُفردة مثل ‎-fx-padding: 8px أو كقائمة من أربع قيم.

يَستقبِل التابع setStyle()‎ مُعامِلًا (parameter) من النوع String يَتَضمَّن قاعدة نمط (style rule) واحدة أو أكثر، ويُطبِقها على مُكوِّن واجهة معين. على سبيل المثال، لنَفْترِض أن المُتْغيِّر message عبارة عن عنوان نصي (label) من النوع Label. لا تَشتمِل العناوين النصية (labels) على حافة (border) أو حشوة (padding) افتراضيًا، ولكن يُمكِن تَخْصِيص أيًا منها كالتالي:

message.setStyle(
   "-fx-padding: 5px; -fx-border-color: black; -fx-border-width: 1px" );

تَضبُط الخاصية ‎-fx-font الخط المُستخدَم لعَرْض النصوص ضِمْن مُكوِّن واجهة. تُخصِّص قيمة تلك الخاصية كُلًا من نوع الخط (font family) وحجمه، كما يُمكِنها أن تُخصِّص وزن الخط (font weight) مثل "bold" و طريقة تَموضُعه (font posture) مثل "italic" أو كليهما. اُنظر الأمثلة التالية:

-fx-font: 30pt "Times New Roman";
-fx-font: bold italic 18pt serif;
-fx-font: bold 42pt monospace;

لابُدّ من إحاطة اسم نوع الخط (font family) بعلامتي اقتباس (quotes) في حالة احتوائه على أي فراغات. تُعدّ أنواع الخطوط "serif" و "monospace" المُخصَّصة بالمثالين الأخيرين وكذلك الأنواع "sans-serif" و "cursive" و "fantasy" أسماءً عامة (generic) أي أنها تُحدِّد نمط عام للخطوط. على سبيل المثال، يُضَاف إلى المحارف (characters) بالنمط "serif" خطوطًا قصيرة لأغراض جمالية أعلى الحرف "I" وأسفله. في المقابل، لا يُضَاف ذلك إلى المحارف بالنمط "sans-serif". أخيرًا، تَحتَل جميع المحارف بالنمط "monospace" نفس المساحة الأفقية، ولذلك فإنها تُستخدَم عادةً عند الحاجة إلى طباعة المحارف بصورة مُصطفّة ضِمْن أعمدة.

يُمكِنك ضَبْط خاصيات أخرى كثيرة باِستخدَام CSS، ولكننا سنكتفي بهذا القدر كما أننا سنَستخدِم CSS فقط لضَبْط الحواف (borders) والحشوات (paddings) وألوان الخلفية والخطوط.

عادةً ما تَكُون عملية ضبط الأنماط (style) مملة خاصة عند وجود عدد كبير من مُكوِّنات الواجهة. في المقابل تُسهِل أوراق الأنماط المتعاقبة (CSS style sheet) من تلك العملية حيث يُمكِنها أن تُطبِق نمطًا معينًا (style) على مُكوِّن مُفرد وحيد أو على مجموعة معينة من المُكوِّنات أو على جميع مُكوِّنات صَنْف معين. ورقة النمط (style sheet) هي عبارة عن ملف بامتداد ‎.css. نَستعرِض خلال المثال التالي ورقة نمط (style sheet) تُطبِق بعضًا من قواعد النمط (style rules) على جميع المكونات من الصَنْفين Label و Button:

Button {
   -fx-font: bold 16pt "Times New Roman";
   -fx-text-fill: darkblue;
}

Label {
   -fx-font: 15pt sans-serif;
   -fx-padding: 7px;
   -fx-border-color: darkred;
   -fx-border-width: 2px;
   -fx-text-fill: darkred;
   -fx-background-color: pink;
}

كأي ملف صورة، يُمكِن لأي ملف ورقة نمط (style sheet) أن يَكُون ملف مورد (resource) ضِمْن البرنامج أي تستطيع تَخْزينه بنفس أماكن تَخْزِين الملفات بامتداد ‎.class. بفَرْض وجود ملف ورقة نمط اسمه "mystyle.css" بالمجلد الرئيسي للبرنامج، نستطيع إذًا أن نُطبِقه على جميع المُكوِّنات الموجودة ضِمْن مشهد (scene) باِستخدَام التَعْليمَة التالية:

scene.getStylesheets().add("mystyle.css");

يُمكِننا إضافة مجموعة من أوراق النمط (style sheets) إلى مشهد (scene) من الصَنْف Scene أو إلى حاويات (containers) مفردة.

ترجمة -بتصرّف- للقسم Section 2: Some Basic Classes من فصل Chapter 6: Introduction to GUI Programming من كتاب Introduction to Programming Using Java.


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...