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

الأشكال الأساسية ولوحة الرسم

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

الأشكال الأساسية ولوحة الرسم

يأتي JavaFX بنظامَين مختلفَين للرسم. الأول هو نظام عقد الأشكال: كائنات تصريحية مثل Rectangle وCircle وLine تعيش بوصفها عقدًا من الدرجة الأولى في رسم البيانات المشهدية، وتستجيب لـ CSS وتستقبل أحداث الماوس ويمكن تحريكها بنفس واجهة برمجة الانتقالات التي تستخدمها لأي عقدة أخرى. الثاني هو Canvas: سطح بكسل ذو وضع فوري يعمل مثل قماش HTML5 — تحصل على GraphicsContext وتصدر أوامر الرسم بشكل إجرائي. لكل نهج مكانه المناسب، وفهم أيهما تختار مهارة محورية.

عقد الأشكال: رسوميات متجهية تصريحية

تمتد جميع فئات الأشكال من javafx.scene.shape.Shape، التي تمتد بدورها من Node. وهذا يعني أن كل شكل يشارك تلقائيًا في التخطيط واختبار الاصطدام والحركات. أكثر الأشكال استخدامًا هي:

  • Rectangle — يُعرَّف بـ x وy وwidth وheight، مع arcWidth/arcHeight الاختياريين لتدوير الزوايا.
  • Circle — يُعرَّف بإحداثيات المركز centerX وcenterY والنصف radius.
  • Ellipse — مثل Circle لكن بنصفَي قطر مستقلَّين radiusX وradiusY.
  • Line — يصل بين نقطتين: من (startX, startY) إلى (endX, endY).
  • Polygon / Polyline — تسلسلات اعتباطية من أزواج إحداثيات x/y.
  • Path — الشكل الأكثر قوةً: تسلسل من كائنات PathElement (MoveTo وLineTo وCubicCurveTo وArcTo وغيرها) يمكنه وصف أي محيط.

فيما يلي مشهد صغير يضع ثلاثة أشكال في Pane ويطبق عليها تعبئة وحدودًا:

import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.scene.shape.Circle; import javafx.scene.shape.Line; import javafx.scene.shape.Rectangle; import javafx.stage.Stage; public class ShapeDemo extends Application { @Override public void start(Stage stage) { // مستطيل بزوايا مدوّرة Rectangle rect = new Rectangle(40, 40, 180, 100); rect.setArcWidth(20); rect.setArcHeight(20); rect.setFill(Color.STEELBLUE); rect.setStroke(Color.DARKBLUE); rect.setStrokeWidth(2); // دائرة بدون تعبئة (حلقة) Circle ring = new Circle(320, 90, 50); ring.setFill(Color.TRANSPARENT); ring.setStroke(Color.CORAL); ring.setStrokeWidth(4); // خط قطري Line diagonal = new Line(30, 200, 420, 200); diagonal.setStroke(Color.DIMGRAY); diagonal.setStrokeWidth(2); diagonal.getStrokeDashArray().addAll(12.0, 6.0); // متقطع Pane pane = new Pane(rect, ring, diagonal); stage.setScene(new Scene(pane, 460, 260)); stage.setTitle("Shape Nodes"); stage.show(); } public static void main(String[] args) { launch(args); } }
التعبئة مقابل الحدود: تلوّن setFill() داخل الشكل؛ أما setStroke() فتلوّن محيطه الخارجي. كلتاهما تقبل أي Paint — سواء Color أو LinearGradient أو RadialGradient. ضبط التعبئة على Color.TRANSPARENT هو الطريقة الصحيحة لإنشاء شكل محيطي فقط.

العمل مع LinearGradient

تُعدّ ألوان الرسم في JavaFX كائنات متكاملة. يتدرّج LinearGradient بين نقاط توقف ألوان على طول متجه اتجاه. وسيطات المنشئ هي: startX وstartY وendX وendY (كلها في النطاق 0–1 عندما يكون proportional=true)، وطريقة الدورة، والعلم التناسبي، وقائمة كائنات Stop.

import javafx.scene.paint.CycleMethod; import javafx.scene.paint.LinearGradient; import javafx.scene.paint.Stop; import javafx.scene.shape.Rectangle; Rectangle gradRect = new Rectangle(50, 50, 200, 100); LinearGradient gradient = new LinearGradient( 0, 0, 1, 0, // من اليسار إلى اليمين true, // تناسبي CycleMethod.NO_CYCLE, new Stop(0.0, Color.DEEPSKYBLUE), new Stop(1.0, Color.MEDIUMPURPLE) ); gradRect.setFill(gradient);

خصائص الحدود

تكشف فئة Shape عن تحكم دقيق في الحدود يعكس نموذج SVG/canvas:

  • setStrokeLineCap(StrokeLineCap.ROUND) — نهايات مدوّرة للمسارات المفتوحة والخطوط.
  • setStrokeLineJoin(StrokeLineJoin.MITER) — كيفية ربط الزوايا.
  • setStrokeDashArray(Double...) — أطوال متناوبة للشرطات والفراغات.
  • setStrokeDashOffset(double) — إزاحة الطور في نمط الشرطات، مفيدة لتحريك "نمل المسير" المتحرك.

واجهة برمجة Canvas: الرسم الفوري

Canvas عقدة ذات مخزن بكسل ثابت. لا تضيف إليها أشكالًا فرعية؛ بدلًا من ذلك تصدر أوامر رسم على GraphicsContext الخاصة بها. التغييرات فورية ودائمة — لا يوجد تسلسل أشكال محتجز يمكن الاستعلام عنه أو تحريكه. وهذا يجعل Canvas الخيار المناسب للمرئيات الكثيفة (الرسوم البيانية وصور الألعاب وخرائط الحرارة) حيث ستكون تكلفة إدارة آلاف عقد رسم البيانات المشهدية الفردية باهظة جدًا.

import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.stage.Stage; public class CanvasDemo extends Application { @Override public void start(Stage stage) { Canvas canvas = new Canvas(480, 300); GraphicsContext gc = canvas.getGraphicsContext2D(); // --- الخلفية --- gc.setFill(Color.web("#1e1e2e")); gc.fillRect(0, 0, 480, 300); // --- دائرة مملوءة --- gc.setFill(Color.CORAL); gc.fillOval(40, 60, 100, 100); // x, y, العرض، الارتفاع (الصندوق المحيط) // --- مستطيل بحدود --- gc.setStroke(Color.LIMEGREEN); gc.setLineWidth(3); gc.strokeRect(200, 60, 120, 80); // --- خط متقطع --- gc.setLineDashes(10, 5); gc.setStroke(Color.GOLD); gc.strokeLine(30, 220, 450, 220); // --- نص --- gc.setFill(Color.WHITE); gc.setFont(Font.font("Monospaced", 16)); gc.fillText("Canvas Demo", 180, 280); // --- منحنى بيزيه --- gc.setStroke(Color.HOTPINK); gc.setLineWidth(2); gc.setLineDashes(); // إعادة ضبط الشرطات gc.beginPath(); gc.moveTo(30, 160); gc.bezierCurveTo(120, 100, 320, 240, 450, 150); gc.stroke(); stage.setScene(new Scene(new StackPane(canvas))); stage.setTitle("Canvas Demo"); stage.show(); } public static void main(String[] args) { launch(args); } }
عقلية آلة الحالة: تحتفظ GraphicsContext بحالة رسم حالية (طلاء التعبئة وطلاء الحدود وعرض الخط والخط والتحويل والقطع). استدع الضوابط قبل كل أمر رسم يحتاجها. استخدم gc.save() / gc.restore() لدفع مكدس الحالة بأكمله وإخراجه، وهو أنظف طريقة لتطبيق تحويل أو قطع مؤقت دون تلويث أوامر الرسم اللاحقة.

مسح اللوحة وإعادة رسمها

بما أن اللوحة مخزن بكسل، فإن تحديثها يعني إعادة الطلاء. النمط المعياري هو مسح المنطقة المتأثرة وإعادة رسم كل شيء:

// داخل AnimationTimer أو معالج حدث: gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight()); drawEverything(gc); // طريقتك المخصصة
Canvas غير محتجز: خلافًا لعقد الأشكال، البكسلات المرسومة على Canvas لا ترتبط بكائنات. لا يمكنك "تحديد" دائرة مرسومة سابقًا وتغيير لونها — يجب إعادة الرسم. إذا احتجت إلى رسوميات تفاعلية قابلة للتحديد فضّل عقد الأشكال. وإذا احتجت إلى رسم آلاف العناصر في كل إطار فضّل Canvas.

حفظ حالة GraphicsContext واستعادتها

gc.save(); // دفع الحالة gc.translate(200, 150); // تحويل مؤقت: نقل نقطة الأصل gc.rotate(45); // دوران 45 درجة gc.setFill(Color.ORANGE); gc.fillRect(-30, -30, 60, 60); // مرتكزة على نقطة الأصل الجديدة gc.restore(); // إخراج الحالة — التحويل اختفى وأُعيد ضبط التعبئة

الاختيار بين عقد الأشكال و Canvas

  • استخدم عقد الأشكال عندما تحتاج إلى أحداث النقر/التحوم على أشكال فردية، أو تنسيق CSS، أو حركات سلسة للخصائص عبر واجهة برمجة الانتقالات.
  • استخدم Canvas عندما ترسم مئات العناصر في كل إطار، أو تنفّذ مخططًا مخصصًا أو حلقة لعبة، أو تحتاج إلى تحكم دقيق على مستوى البكسل.
  • امزج بينهما بحرية: Canvas مجرد Node — يمكنك وضعها في نفس Pane مع عقد أشكال، وطبقة خلفية مرسومة بالبكسل خلف عناصر تحكم متجهية تفاعلية.

الخلاصة

يمنحك JavaFX أداتَي رسم قويتَين. عقد الأشكال — Rectangle وCircle وPath وأقاربها — تعيش في رسم البيانات المشهدية وتستجيب لـ CSS والأحداث، وهي الخيار الافتراضي الصحيح لعناصر واجهة المستخدم التفاعلية. أما Canvas مع GraphicsContext فهي سطح ذو وضع فوري يتفوق في التصيير الكثيف عالي التردد. أتقن كليهما، وافهم نموذج آلة الحالة في GraphicsContext، واستخدم save()/restore() للحفاظ على روتينات الرسم المعقدة نظيفة وقابلة للتركيب.