أساسيات JavaFX ورسم المشهد

الألوان والخطوط والنصوص

18 دقيقة الدرس 7 من 12

الألوان والخطوط والنصوص

بعد أن تعلّمت وضع الأشكال على رسم المشهد، تأتي مرحلة إتقان تنسيق النصوص — وهي مهارة لا غنى عنها لكل مطوّر JavaFX. الطباعة والألوان ليستا مجرد لمسات جمالية، بل تحملان التسلسل الهرمي وهوية التطبيق وسهولة القراءة. تمنحك JavaFX نظامًا ثريًا من القيم الثابتة (immutable value types) لكليهما، وفهم هذا النظام سيجنّبك الوقوع في الأخطاء الشائعة المتعلقة بإعادة استخدام الكائنات وتجاوزات CSS.

فئة Color

الفئة javafx.scene.paint.Color نوع قيمة ثابت (immutable). لا تُعدّل كائن Color أبدًا — بل تُنشئ كائنًا جديدًا عوضًا عن ذلك. تأتي JavaFX مزوّدة بنحو 150 ثابتًا مسمّى (Color.DODGERBLUE، Color.CORAL وغيرها)، لكنك ستلجأ في التطبيقات الحقيقية غالبًا إلى إحدى طرق المصنع الثلاث التالية.

RGB (أعداد عشرية من 0.0 إلى 1.0):

Color c1 = Color.color(0.2, 0.6, 1.0); // r, g, b — من 0.0 إلى 1.0 Color c2 = Color.color(0.2, 0.6, 1.0, 0.8); // الوسيط الرابع هو الشفافية (alpha)

RGB (أعداد صحيحة من 0 إلى 255):

Color c3 = Color.rgb(51, 153, 255); // قيم 8-bit المألوفة Color c4 = Color.rgb(51, 153, 255, 0.8); // مع الشفافية

سلسلة Hex (بأسلوب CSS):

Color c5 = Color.web("#3399ff"); // hex سداسي قياسي Color c6 = Color.web("#3399ff", 0.8); // hex مع الشفافية Color c7 = Color.web("rgba(51,153,255,0.8)"); // يقبل أيضًا CSS rgba()
استخدم Color.web() عند العمل من مواصفات تصميم. يُسلّم المصمّمون رموز hex. تقبلها Color.web() مباشرةً، ممّا يُلغي خطوة التحويل اليدوي ويجعل الكود موثّقًا ذاتيًا.

تطبيق الألوان على عقد النص

في JavaFX، فئة عرض النص هي javafx.scene.text.Text. يُضبط لون التعبئة (اللون الأمامي) عبر setFill(Paint). تنفّذ Color الواجهة Paint، لذا تمرّر كائن Color مباشرةً.

import javafx.scene.text.Text; import javafx.scene.paint.Color; Text heading = new Text("مرحبًا بـ JavaFX"); heading.setFill(Color.web("#1a1a2e")); heading.setStroke(Color.web("#3399ff")); // حدّ خارجي اختياري heading.setStrokeWidth(0.5);

لضبط ألوان خلفية حاويات التخطيط (VBox، HBox وغيرها)، استخدم سلسلة نمط مضمّنة، إذ لا تمتلك هذه العقد تابع setFill مباشر:

VBox root = new VBox(10); root.setStyle("-fx-background-color: #f0f4ff;");

فئة Font

الفئة javafx.scene.text.Font ثابتة هي الأخرى. كل كائن Font لقطة من العائلة والوزن والوضعية والحجم. تُنشئ الخطوط عبر طرق مصنع ثابتة (static factory methods).

الخط الافتراضي بحجم محدد:

Font small = Font.font(12); Font normal = Font.font(14); Font large = Font.font(24);

عائلة مسمّاة وحجم محدد:

Font sansSerif = Font.font("Arial", 16); Font mono = Font.font("Courier New", 14);

عائلة مسمّاة مع وزن و/أو وضعية:

import javafx.scene.text.FontWeight; import javafx.scene.text.FontPosture; Font bold = Font.font("Segoe UI", FontWeight.BOLD, 18); Font italic = Font.font("Georgia", FontPosture.ITALIC, 16); Font boldItalic = Font.font("Georgia", FontWeight.BOLD, FontPosture.ITALIC, 16);
توافر الخطوط يعتمد على نظام التشغيل. سيعمل Font.font("Segoe UI", ...) على Windows لكنه سيعود تلقائيًا إلى الخط الافتراضي للنظام على macOS أو Linux. للتطبيقات متعددة المنصات، حمّل ملف خط مُضمَّن أو اختر عائلة متاحة عالميًا كـ Arial أو Courier New.

تحميل خط مخصص من الموارد

تضمين خط في ملف JAR وتحميله عند التشغيل هو الأسلوب الصحيح لأي تطبيق يجب أن يبدو متطابقًا على كل نظام تشغيل.

// ضع الملف في: src/main/resources/fonts/Roboto-Regular.ttf Font roboto = Font.loadFont( getClass().getResourceAsStream("/fonts/Roboto-Regular.ttf"), 16 // الحجم المطلوب ); if (roboto == null) { // الملف غير موجود — اعود إلى الخط الاحتياطي بأناقة roboto = Font.font("Arial", 16); }

بعد التحميل تُسجّل JavaFX اسم عائلة الخط من داخل بيانات TTF/OTF الوصفية، فيمكنك لاحقًا الإشارة إليه عبر Font.font("Roboto", ...) في الجلسة ذاتها.

تطبيق الخطوط على عقد النص

import javafx.scene.text.Text; import javafx.scene.text.Font; import javafx.scene.text.FontWeight; import javafx.scene.paint.Color; Text title = new Text("طباعة JavaFX"); title.setFont(Font.font("Arial", FontWeight.BOLD, 28)); title.setFill(Color.web("#1a1a2e")); Text body = new Text("الخطوط والألوان تمنح النصوص التسلسل الهرمي والشخصية."); body.setFont(Font.font("Arial", 14)); body.setFill(Color.web("#444444"));

التفاف النص والمحاذاة

تُعرض عقدة Text العادية على سطر واحد افتراضيًا. اضبط wrappingWidth لتفعيل التفاف تلقائي:

Text paragraph = new Text( "توفّر JavaFX واجهة برمجية ثرية للنصوص مع دعم متعدد الأسطر " + "وخطوط مخصصة وتغطية كاملة لـ Unicode." ); paragraph.setFont(Font.font("Arial", 14)); paragraph.setFill(Color.web("#333333")); paragraph.setWrappingWidth(300); // التفاف عند 300 بكسل منطقي

لكتل النصوص الأغنى داخل التخطيطات، غالبًا ما يكون javafx.scene.control.Label أكثر ملاءمة من عقدة Text الخام — إذ يتكامل مع نظام التخطيط ويدعم setAlignment() وsetWrapText(true).

import javafx.scene.control.Label; import javafx.geometry.Pos; Label label = new Label("مرحبًا بالتطبيق"); label.setFont(Font.font("Arial", FontWeight.BOLD, 18)); label.setTextFill(Color.web("#1a1a2e")); // ملاحظة: Label تستخدم setTextFill وليس setFill label.setAlignment(Pos.CENTER); label.setWrapText(true);
الفرق بين Label.setTextFill() وText.setFill() — أسماء التوابع مختلفة! ترث Text من Shape وتستخدم setFill(Paint). ترث Label من Control وتستخدم setTextFill(Paint). استدعاء التابع الخاطئ يُصرَّف (compile) بلا أخطاء لكنه لا ينتج أي أثر مرئي.

تجميع كل شيء — بطاقة منسّقة

import javafx.application.Application; import javafx.geometry.Insets; import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.scene.text.FontWeight; import javafx.stage.Stage; public class StyledCardApp extends Application { @Override public void start(Stage primaryStage) { Label title = new Label("عرض طباعة JavaFX"); title.setFont(Font.font("Arial", FontWeight.BOLD, 22)); title.setTextFill(Color.web("#1a1a2e")); Label subtitle = new Label("الألوان والخطوط والنصوص — الدرس 7"); subtitle.setFont(Font.font("Arial", 14)); subtitle.setTextFill(Color.web("#6c757d")); Label body = new Label( "فئتا Font و Color في JavaFX أنواع قيم ثابتة. " + "كل استدعاء يُنشئ كائنًا جديدًا بدلًا من تغيير الحالة، " + "مما يجعلهما آمنتَين للمشاركة بين الخيوط (threads)." ); body.setFont(Font.font("Arial", 13)); body.setTextFill(Color.web("#343a40")); body.setWrapText(true); body.setMaxWidth(360); VBox card = new VBox(10, title, subtitle, body); card.setPadding(new Insets(24)); card.setStyle("-fx-background-color: #ffffff; -fx-border-color: #dee2e6; " + "-fx-border-radius: 8; -fx-background-radius: 8;"); VBox root = new VBox(card); root.setPadding(new Insets(32)); root.setStyle("-fx-background-color: #f8f9fa;"); primaryStage.setScene(new Scene(root, 440, 240)); primaryStage.setTitle("بطاقة منسّقة"); primaryStage.show(); } public static void main(String[] args) { launch(args); } }

الخلاصة

كائنا Color وFont لقطات ثابتة لا تتغيّر — تُنشئهما ولا تُعدّلهما. استخدم Color.web() لرموز hex التي يُسلّمها المصمّمون، وحمّل الخطوط المخصصة عند بدء التشغيل لضمان تصيير متسق. طبّق الألوان بـsetFill() على عقد Text وبـsetTextFill() على عناصر تحكم Label — فكلتاهما واجهتان مختلفتان على تسلسلَي كلاس منفصلَين. في الدرس القادم ستُضيف التفاعلية بمعالجة أول حدث للمستخدم.