פרק 17: הסביבה הגרפית Morphic • סעיף 17.1 הקדמה ועקרונות יסוד הנדסיים

הקדמה ל-Morphic: ישירות (Directness), חיוּת (Liveness) והיררכיית המחלקה Morph

איך סביבת המשתמש הגרפית של Squeak הופכת כל פיקסל וצורה על המסך לעצם חי בזיכרון, איך מפרקים את תפריט העולם באמצעות הילת ה-Halo וצלמית ה-pickup, ואיך כל עצם או צבע הופך לרכיב גרפי בלחיצת הודעה (`asMorph`).

🎨
🧠

מודל מנטלי ועיקרון מנחה: אין "ציור מת" על המסך — כל צורה היא עצם חי שאפשר לגעת בו!

💡 האנלוגיה המוחשית

ברוב מערכות ההפעלה, מה שאתה רואה על המסך הוא כמו תמונה מצולמת של חפץ — אי אפשר לתפוס כפתור מתוך תפריט ולשלוף אותו החוצה. ב-Morphic, המסך הוא שולחן עבודה פיזי של לגו: כל תפריט, טקסט או מלבן הוא קוביית לגו חיה שאתה יכול לתפוס, לפרק ולשנות בזמן אמת!

⚖️ חוק הברזל הפדגוגי

הסביבה הגרפית Morphic נשענת על שני עקרונות יסוד הנדסיים בלתי ניתנים להפרדה: ישירוּת (Directness) — הצורות על המסך הן העצמים עצמם, וחיוּת (Liveness) — הסביבה מגיבה למשתמש באופן רציף והמידע מתעדכן חי מול עיניך.

🎯 תובנת המפתח

המחלקה Morph (היורשת מ-Object) ניצבת בראש היררכיית הממשק הגרפי, וכל עצם בסמולטוק (מחרוזת, צבע וכו') יודע להפוך את עצמו לרכיב גרפי חי באמצעות שליחת ההודעה asMorph והצגתו בעולם עם openInWorld!

1. שני עקרונות היסוד של Morphic: ישירוּת (Directness) וחיוּת (Liveness)

למה ממשק המשתמש הגרפי של Squeak שונה מכל ממשק חלונות שהכרת?

בוא נפתח יחד דף חדש ומרתק במסע שלנו! עד עכשיו דיברנו על עצמים, מחלקות והודעות שחיים בתוך הזיכרון של Smalltalk. עכשיו אנחנו מגיעים אל הסביבה הגרפית Morphic — ממשק המשתמש הגרפי של סביבת Squeak, שבו כל אלמנט חזותי שאתה רואה על המסך הוא עצם חי בזיכרון, הניתן לתמרון, עריכה ותכנות בזמן אמת.

כדי להבין את הפילוסופיה ההנדסית של Morphic, אני רוצה שתכיר את שני עמודי התווך שמנחים אותה בכל רגע:

👆 1. ישירוּת (Directness)

כל הצורות והאלמנטים המוצגים על גבי המסך הם העצמים עצמם! אין כאן הפרדה מלאכותית בין "ציור על המסך" לבין "אובייקט בקוד". ניתן לבחון כל צורה על המסך, לתפעל אותה ולשנות את מצבה בצורה ישירה ומוחשית לחלוטין.

⚡ 2. חיוּת (Liveness)

הסביבה מגיבה אל המשתמש באופן רציף ודינמי, והמידע על המסך מתעדכן באופן חי (Live) בעקבות כל פעולה או שינוי. אינך צריך "לקמפל מחדש ולהריץ" כדי לראות שינוי בצורה או בקואורדינטות שלה!

2. עקרון ה-Morphic Halo ותרגיל פירוק תפריט העולם (World Menu)

איך לוקחים פריט מתוך תפריט המערכת והופכים אותו לכפתור עצמאי על שולחן העבודה?

איך אנחנו נוגעים בעצם גרפי באופן ישיר? לשם כך קיימת הילת מורפיק (Morphic Halo): כאשר אנחנו לוחצים על הכפתור הכחול (הכפתור האמצעי / גלגלת) של העכבר מעל כל אלמנט חזותי ב-Squeak, הסביבה מקיפה את אותו אלמנט במסגרת של צלמיות שליטה צבעוניות (Halo Icons) המאפשרות הזזה, סיבוב, שינוי גודל, שכפול, פתיחת תפריט חקירה ומחיקה.

כדי שתרגיש בעצמך עד כמה עקרון הישירוּת מרחיק לכת, בוא נעשה יחד את תרגיל ניתוק פריט מתפריט העולם (`World Menu`) בחמישה צעדים פשוטים:

צעד 1 פתיחת תפריט העולם

לוחצים לחיצה שמאלית על רקע הסביבה (העולם) כדי להציג את תפריט העולם (World Menu).

צעד 2 Halo על התפריט כולו

לוחצים על הכפתור האמצעי (הכחול) מעל אזור התפריט כדי להקיף את התפריט כולו במסגרת צלמיות (Halo).

צעד 3 Halo על פריט בודד

לחיצה נוספת בכפתור האמצעי (הכחול) מעל אחד מפריטי התפריט (למשל help…) תקיף אך ורק את אותו פריט בודד ב-Halo ייעודי משלו!

צעד 4 גרירה בצלמית ה-pickup

תופסים את צלמית ה-pickup (הצלמית השחורה הממוקמת במרכז השורה העליונה של ה-Halo) וגוררים את הפריט החוצה מתוך התפריט.

צעד 5 · התוצאה! כפתור פעולה עצמאי!

הפריט help… מנותק כעת מתפריט העולם, ומתפקד על שולחן העבודה ככפתור פעולה עצמאי לחלוטין!

3. היררכיית המחלקות של Morphic: הכל מתחיל במחלקה Morph

הכרת עץ ההורשה מ-Object דרך Morph ועד ל-BorderedMorph, StringMorph ו-HaloMorph

מאחורי הקלעים של הקסם הזה ניצבת ארכיטקטורת מחלקות מופתית. המחלקה Morph (היורשת ישירות מ-Object) עומדת בראש היררכיית רכיבי הממשק הגרפי. ממנה מתפצלות תת-מחלקות ייעודיות לכל סוג של רכיב חזותי — החל מרכיבים בעלי מסגרת ושוליים (BorderedMorph וממנו RectangleMorph, EllipseMorph, PolygonMorph, WireMorph, StarMorph, AlignmentMorph, MenuMorph) ועד לרכיבי תמונה, טקסט, מדי התקדמות ואפילו ההילות עצמן (HaloMorph)!

עזר גרפי 17.1 • עץ ההורשה המרכזי של סביבת Morphic

היררכיית המחלקות תחת Object → Morph

Morphic Class Tree
Object Morph BorderedMorph ImageMorph StringMorph TextMorph HaloMorph ProgressBarMorph BalloonMorph RectangleMorph EllipseMorph PolygonMorph AlignmentMorph MenuMorph WireMorph StarMorph

4. הפיכת כל עצם וצבע לרכיב גרפי: asMorph, openInWorld ו-openViewerForArgument

איך מחרוזת טקסט או עצם מסוג Color הופכים לצורה חיה על שולחן העבודה?

בזכות הכוח של Smalltalk, כל עצם ניתן להמרה לרכיב גרפי באמצעות שליחת ההודעה הפשוטה asMorph! בוא נראה מה קורה כשאנחנו לוקחים מחרוזת טקסט רגילה ומזמינים אותה אל העולם הגרפי:

s := 'Hello' asMorph openInWorld.
s openViewerForArgument.
  • הביטוי 'Hello' asMorph ממיר את המחרוזת ומחזיר מופע של המחלקה StringMorph.
  • שליחת ההודעה openInWorld מציגה את הרכיב ישירות על גבי שולחן העבודה (העולם הגרפי של Squeak).
  • שליחת ההודעה openViewerForArgument פותחת חלון אינטראקציה חכם (Viewer) המציג את תכונות הרכיב (כגון קואורדינטות וזווית כיוון המתעדכנות בזמן אמת כשמזיזים את הצורה!), כולל משולשים קטנים לקביעת ערכי פרמטרים ולחיצים לשליחת הודעות תנועה וצליל ישירות אל העצם!
🤔

תרגיל למחשבה: כיצד ניתן לגרום לעצם גרפי "לצחוק"?

בתוך חלון ה-Viewer שנפתח באמצעות s openViewerForArgument (או מתוך ה-Halo של הרכיב), קיימות קטגוריות של פקודות מוכנות עבור הרכיב — וביניהן פקודות תנועה וצליל (כגון make sound עם צליל צחוק או שליחת הודעת השמעת צליל ישירות לעצם)! לחיצה על סימן הקריאה שלצד הפקודה ב-Viewer גורמת לעצם הגרפי להשמיע צליל צחוק מיד!

ומה קורה כשאנחנו שולחים את ההודעה asMorph אל עצם המייצג צבע, כמו בביטוי Color red asMorph openInWorld? מיד מופיע מלבן אדום בפינה השמאלית-העליונה של המסך! בוא נסתכל על מימוש המתודה Color>>asMorph ונגלה איך זה עובד:

asMorph
    ^ (RectangleMorph new)
        fillStyle: self;
        borderWidth: 0;
        yourself

שים לב לאלגנטיות: עצם הצבע (self) יוצר מופע חדש של RectangleMorph, קובע את צבע המילוי שלו (fillStyle: self) להיות הצבע עצמו, מאפס את עובי המסגרת (borderWidth: 0), ומחזיר את המלבן הנוצר בעזרת yourself!

🔍

מיני-סימולציה 17.1: סייר מחלקות עץ ה-Morphic

לחץ על כל מחלקה כדי לראות את תפקידה בהיררכיה
🕹️
סימולציה אינטראקטיבית גדולה 17.1

מעבדת הישירוּת והחיוּת: פירוק תפריט העולם עם ה-Halo, הרצת asMorph וחלון ה-Viewer

Squeak World Desktop (Directness & Liveness) שלב נוכחי: שולחן עבודה מוכן
World
open...
projects...
help...
save
1. תרגיל פירוק תפריט העולם עם ה-Halo:
2. הרצת קוד Smalltalk חי בעולם:
לחץ על כפתורי השלבים כדי לחוות את עקרונות הישירות והחיות של Morphic!
⚡

הגשר הנרטיבי: הצגנו צורות חיות על המסך – אבל איך שולטים במיקום, בגודל ובצבע שלהן מתוך הקוד?

ראינו שפקודה כמו Color red asMorph openInWorld מציירת מלבן אדום בפינה השמאלית-העליונה של המסך. למה דווקא בפינה השמאלית-העליונה? איך בנויה מערכת הצירים של המסך ב-Squeak, ומדוע ציר ה-y שלה מתקדם כלפי מטה? בוא נעבור לסעיף 17.2 ונשלוט במתודות המיקום, הממדים והצבע!

סוף יחידה 17.1 • הקדמה, עקרונות והיררכיית Morphic
פרק 17: הסביבה הגרפית Morphic • סעיף 17.2 גיאומטריה של המסך ומתודות בסיסיות

מתודות Morph בסיסיות ומערכת הצירים של המסך (position, extent, color)

הכרת מערכת הצירים שבה ראשית הצירים (0,0) בפינה השמאלית-העליונה וציר ה-y חיובי כלפי מטה, המלבן החוסם (Bounding Box), ומתודות הגישה והעדכון למיקום, ממדים וצבע.

📏
🧠

מודל מנטלי ועיקרון מנחה: במסך המחשב ראשית הצירים (0,0) נמצאת למעלה משמאל, וציר ה-y יורד למטה!

💡 האנלוגיה המוחשית

תחשוב על דף באנגלית או על קרן שסורקת מסך טלוויזיה מלמעלה למטה: אנחנו מתחילים בפינה השמאלית-העליונה (0,0), מתקדמים ימינה ככל ש-x גדל, ויורדים שורה-שורה כלפי מטה ככל ש-y גדל!

⚖️ חוק הברזל הפדגוגי

המיקום (position) והממדים (extent) של כל צורה ב-Morphic מוגדרים תמיד כעצמים מסוג נקודה (Point בצורה x@y או width@height) ביחס לפינה השמאלית-עליונה של המלבן החוסם (Bounding Box) שלה.

🎯 תובנת המפתח

מכיוון שנקודות תומכות בחיבור וקטורי טבעי, הזזת צורה ב-10 פיקסלים ימינה ו-10 פיקסלים מטה מתבצעת בביטוי האלגנטי: aMorph position: (aMorph position + (10@10)).

1. מערכת הצירים של המסך והמלבן החוסם (Bounding Box)

למה (0,0) נמצאת בפינה השמאלית-העליונה ואיך נקבע מיקומה של כל צורה?

לפני שנזיז צורות על המסך, אנחנו חייבים להכיר את "מפת הדרכים" של המסך ב-Squeak. בניגוד למערכת הצירים הקרטזית שאתה מכיר משיעורי מתמטיקה (שבה ציר ה-y עולה כלפי מעלה), במערכות גרפיקה ממוחשבות מערכת הצירים של המסך פועלת כך:

  • ראשית הצירים (0,0) ממוקמת בפינה השמאלית-העליונה של המסך.
  • ערכים חיוביים של ציר x מתקדמים ימינה.
  • ערכים חיוביים של ציר y מתקדמים כלפי מטה.
  • המלבן החוסם (Bounding Box): גם אם הצורה שלנו היא כוכב או עיגול, מיקומה (position) מוגדר תמיד ביחס לפינה השמאלית-עליונה של המלבן החוסם שלה!
💡

תיבת מחשבה: מדוע ציר ה-y במחשבים הפוך ממתמטיקה קלאסית (גדל כלפי מטה)?

היסטורית, מסכי המחשב הראשונים (מסכי קתודה CRT) סרקו את התמונה באמצעות קרן אלקטרונים שהתחילה בפינה השמאלית-העליונה של המסך (שורה 0, עמודה 0), רצה משמאל לימין לאורך השורה, וירדה שורה אחר שורה כלפי מטה — ממש כמו שאנחנו כותבים וקוראים טקסט מדף מלמעלה למטה! לכן קואורדינטת y מייצגת כמה שורות פיקסלים ירדנו מראש המסך.

2. מתודות הגישה והעדכון המרכזיות של Morph

שליטה מלאה במיקום (position), בממדים (extent) ובצבע (color) באמצעות עצמי Point ו-Color

position / position:

מיקום הצורה על המסך

• position: מחזירה מופע של Point (למשל 50@100) המציין את מיקום הפינה השמאלית-עליונה של הצורה.
• position: aPoint: מעבירה ומציבה את הצורה במיקום המבוקש.

extent / extent:

ממדי המלבן החוסם

• extent: מחזירה נקודה (width @ height) המייצגת את רוחב וגובה המלבן החוסם של הצורה.
• extent: aPoint: משנה וקובעת את ממדי הצורה.

color: aColor

צבע הצורה

• color: aColor: מקבלת עצם מסוג Color ומשנה מיד את צבע הצורה על המסך (למשל Color yellow, Color green או Color blue).

לדוגמה, כדי להזיז צורה קיימת aMorph ב-10 פיקסלים ימינה וב-10 פיקסלים מטה (תנועה באלכסון), אנחנו שואלים אותה מה מיקומה הנוכחי, מוסיפים לו את הנקודה 10@10 ומציבים בחזרה:

aMorph position: (aMorph position + (10@10))

3. תרגיל מעשי מסכם: הצורה הצהובה והצורה הירוקה

יישום כל מתודות המיקום, הממדים והצבע בתרגיל מודרך שלב אחר שלב

בוא נפתור יחד את התרגיל המעשי המלא: ניצור שתי צורות מסוג Morph — אחת צהובה ואחת ירוקה — ונבצע עליהן סדרה של פעולות גיאומטריות:

"1. יצירת צורה צהובה וצורה ירוקה והצגתן בעולם"
yellowMorph := Morph new color: Color yellow; openInWorld.
greenMorph := Morph new color: Color green; openInWorld.

"2. הזזת הצורה הצהובה 10 פיקסלים ימינה, 10 פעמים"
10 timesRepeat: [ yellowMorph position: (yellowMorph position + (10@0)) ].

"3. הזזת הצורה הירוקה באלכסון (10 ימינה ו-10 למטה), 10 פעמים"
10 timesRepeat: [ greenMorph position: (greenMorph position + (10@10)) ].

"4. לאחר הגדלת הצורה הצהובה בעכבר, השוואת גודל הירוקה לזה של הצהובה"
greenMorph extent: yellowMorph extent.

"5. הצגת המיקום והגודל ושינוי צבע הצורה הצהובה לכחול"
Transcript show: yellowMorph position; cr; show: yellowMorph extent; cr.
yellowMorph color: Color blue.
עזר גרפי 17.2 • אנטומיית מערכת הצירים והמלבן החוסם

ראשית הצירים (0,0) בפינה השמאלית-העליונה, position ו-extent

Screen Coordinates (x@y)
(0 @ 0) Origin - פינה שמאלית עליונה +X (ימינה) → ↓ +Y (כלפי מטה!) position: 150 @ 80 aMorph (Bounding Box) ← width (רוחב) → extent: width @ height
🧮

מיני-סימולציה 17.2: בוחן מתודות הגיאומטריה של Morph

לחץ על כל מתודה להמחשה חיה
📐
סימולציה אינטראקטיבית גדולה 17.2

מעבדת מערכת הצירים (XY Grid Sandbox): תרגיל הצורה הצהובה והירוקה

(0@0) Top-Left Origin yellow: pos(20@35) ext(60@45) | green: pos(20@110) ext(60@45)
yellow
green
הרץ את שלבי התרגיל בזמן אמת:
בחר פעולה כדי לראות את השינוי במיקום ובממדים על רשת הצירים!
⚡

הגשר הנרטיבי: שלטנו בצורות נפרדות – אבל איך מרכיבים צורה אחת בתוך צורה אחרת כך שינועו יחד?

בממשק גרפי אמיתי, חלון מכיל כפתורים, וכפתור מכיל תווית טקסט או אייקון. כשאנחנו מזיזים את החלון, אנחנו מצפים שכל הרכיבים שבתוכו יזוזו יחד איתו אוטומטית! איך בונים עץ הכלה היררכי כזה ב-Morphic בעזרת addMorph: ו-removeMorph:? בוא נעבור לסעיף 17.3!

פרק 17: הסביבה הגרפית Morphic • סעיף 17.3 Containment Hierarchy / Submorphs

יחס הכלה והרכבת צורות (Composition): addMorph:, removeMorph: ומרכוז

איך בונים עץ היררכי שבו רכיב אב (Owner) מכיל תת-רכיבים (Submorphs), כיצד הזזת המלבן המכיל גוררת איתה את הכוכב המוכל, מרכוז בעזרת center:, והרכבה או הפרדה דרך הקוד או דרך הממשק הגרפי.

🧩
🧠

מודל מנטלי ועיקרון מנחה: עץ הכלה היררכי — כשהאב זז, כל ילדיו (Submorphs) זזים יחד איתו!

💡 האנלוגיה המוחשית

תחשוב על מדבקה של כוכב צהוב שהדבקת על קופסת קרטון אדומה: אם תזיז את הקופסה האדומה על השולחן, הכוכב יזוז יחד איתה אוטומטית. אם תזיז את הכוכב עצמו קצת הצידה על גבי הקופסה, ואז תזיז שוב את הקופסה — הכוכב עדיין ייסע יחד עם הקופסה!

⚖️ חוק הברזל הפדגוגי

המתודה rect addMorph: star מוסיפה את הכוכב לרשימת ה-submorphs של המלבן. המתודה rect removeMorph: star מנתקת אותו ומסירה אותו מהמסך, אך הוא ממשיך לחיות בזיכרון וניתן להציגו כעצמאי עם star openInWorld.

🎯 תובנת המפתח

ב-Morphic כל פעולת קוד קיימת גם באופן חזותי ישיר: אפשר לשלב צורות דרך תפריט ההקשר (קליק ימני/צהוב → embed → into) ולנתק אותן בלחיצה כפולה על הכפתור האמצעי (הכחול) וגרירת ההילה הפנימית החוצה!

1. הרכבת צורות בקוד באמצעות addMorph:

בניית יחס אב-בן (Parent/Owner מול Child/Submorphs) בין מלבן אדום לכוכב צהוב

רכיבי Morph אינם חיים רק כצורות בודדות על המסך — הם בנויים במבנה של עץ הכלה היררכי (Containment Hierarchy), שבו רכיב אחד משמש כמכיל (Parent / Owner) של רכיבים אחרים המכונים תת-רכיבים (Child / Submorphs).

בוא נבחן את קטע הקוד היוצר מלבן אדום ומכניס לתוכו כוכב צהוב (StarMorph):

rect := Morph new color: Color red.
rect openInWorld.
star := StarMorph new color: Color yellow.
rect addMorph: star.
star position: rect position.
  • ההודעה rect addMorph: star מוסיפה את הרכיב star לתוך רשימת ה-submorphs של rect.
  • ההודעה star position: rect position מצמידה את הפינה השמאלית-עליונה של המלבן החוסם של הכוכב לפינה השמאלית-עליונה של המלבן האדום.
  • מרכוז מדויק (Geometry protocol): אם נרצה למרכז את הכוכב בדיוק במרכז המלבן, נוכל להשתמש במתודה center: ולכתוב: star center: rect center!

2. התנהגות בעת תנועה וניתוק תלות באמצעות removeMorph:

מה קורה כשמזיזים את האב, מה קורה כשמזיזים את הבן, ואיך מפרידים ביניהם?

🚀 א. תנועה של המלבן המכיל מול הכוכב המוכל

• הזזת המלבן המכיל: אם נריץ rect position: (rect position + (10@10)), שתי הצורות נעות יחדיו! הכוכב זז אוטומטית יחד עם המלבן שמכיל אותו.
• הזזת הכוכב המוכל ישירות: אם נריץ star position: (star position + (10@10)), הכוכב נע ביחס למקומו, אך עדיין נשאר מוכל במלבן — כך שאם נזיז שוב את המלבן, הכוכב עדיין ינוע יחד איתו!

✂️ ב. הפרדת רכיבים בקוד (removeMorph:)

• הפקודה rect removeMorph: star מנתקת את הכוכב מרשימת ה-submorphs של המלבן.
• ברגע הניתוק הכוכב מוסר מהמסך, אך הוא ממשיך להתקיים כעצם חי בזיכרון!
• ניתן להציגו מחדש כרכיב עצמאי לחלוטין בעולם באמצעות הפקודה star openInWorld.

3. הרכבה והפרדה חזותית באמצעות הממשק הגרפי וה-Halo

איך מבצעים embed into וניתוק צורות בעכבר מבלי לכתוב שורת קוד אחת?

ברוח עקרון הישירוּת (Directness) של Morphic, כל פעולת הרכבה והפרדה שעשינו בקוד ניתנת לביצוע גם באופן חזותי ישיר עם העכבר:

  • הרכבה חזותית (embed into): פותחים את תפריט העולם → בוחרים בפריט objects (o) → לוחצים על כפתור Graphics לפתיחת חלון בחירת צורות → גוררים שתי צורות ומניחים אותן זו על גבי זו → לוחצים לחיצה ימנית (צהובה) על הצורה העליונה לפתיחת תפריט ההקשר → בוחרים בתפריט-הבת embed → ובוחרים into כדי לשלב את הצורה העליונה בתוך הצורה שמתחתיה!
  • הפרדה חזותית באמצעות ה-Halo: לחיצה אחת על הכפתור האמצעי (הכחול) מעלה את ה-Halo של הצורה המכילה (האב). לחיצה שנייה (כפולה) על הכפתור האמצעי מעלה את ה-Halo של הצורה הפנימית המוכלת (הכוכב); גרירתה החוצה באמצעות סמן העכבר מנתקת אותה אוטומטית מצורת האב!
עזר גרפי 17.3 • יחס הכלה (Owner / Submorphs)

השוואה בין צורות נפרדות לבין צורה מוכלת (rect addMorph: star)

Parent-Child Submorphs
1. לפני addMorph: (או אחרי removeMorph:) rect (אדום) שני עצמים נפרדים בעולם — הזזת המלבן אינה מזיזה את הכוכב 2. אחרי rect addMorph: star (ו-star center: rect center) הכוכב ב-submorphs של המלבן — שניהם נעים יחדיו כיחידה אחת!
🔗

מיני-סימולציה 17.3: חוקר פעולות ההרכבה וההפרדה (קוד מול GUI)

לחץ על כל פעולה להסבר מדויק
⭐
סימולציה אינטראקטיבית גדולה 17.3

מעבדת ההכלה החיה: המלבן האדום (rect) והכוכב הצהוב (star)

מצב: מוכל (rect addMorph: star) rect: (40@45) | star: (40@45)
rect
⭐
בדוק את התנהגות ההכלה והתנועה:
נסה להזיז את המלבן כשהכוכב בתוכו לעומת לאחר הפרדתו!
⚡

הגשר הנרטיבי: הרכבנו צורות קיימות – אבל איך יוצרים צורה גרפית חדשה לגמרי בעלת מראה ייחודי?

עד עכשיו השתמשנו במחלקות מוכנות כמו RectangleMorph ו-StarMorph. אבל מה אם אנחנו רוצים להגדיר רכיב בעל מראה ייחודי משלנו — למשל צורת צלב (CrossMorph)? איך מערכת Squeak מציירת רכיבים באמצעות drawOn: aCanvas, ואיך מבטיחים שהצלב יגיב ללחיצות עכבר רק על שטח הצלב עצמו ולא על הפינות הריקות של המלבן החוסם? בוא נעבור לסעיף 17.4!

פרק 17: הסביבה הגרפית Morphic • סעיף 17.4 ציור מותאם אישית וזיהוי פגיעת עכבר

יצירת צורות גרפיות: drawOn: aCanvas, המחלקה CrossMorph ודריסת containsPoint:

איך יוצרים תת-מחלקה של Morph ודורסים את מתודת הציור drawOn: aCanvas, חישוב הזרוע האופקית והאנכית של הצלב באמצעות bounds insetBy:, ודיוק משטח הלחיצה באמצעות דריסת containsPoint: aPoint.

🖌️
🧠

מודל מנטלי ועיקרון מנחה: אתה לא מצייר ישירות למסך — הסביבה נותנת לך בד ציור (Canvas) ומבקשת ממך: drawOn:!

💡 האנלוגיה המוחשית

תחשוב על צייר שמקבל כן ציור (aCanvas): בכל פעם שצריך לרענן את המסך, מערכת Squeak ניגשת אל הצורה שלך ושולחת לה אוטומטית את ההודעה drawOn: aCanvas — והצורה מציירת את עצמה על הקנבס!

⚖️ חוק הברזל הפדגוגי

כברירת מחדל, כל Morph מגיב ללחיצות עכבר בכל שטח המלבן החוסם שלו (bounds). אם הצורה שלך אינה מלבנית מלאה (כמו CrossMorph), חובה לדרוס גם את containsPoint: aPoint כדי שהפינות הריקות לא יגיבו ללחיצות!

🎯 תובנת המפתח

המתודה bounds insetBy: aPoint של מחלקת Rectangle מכווצת את גבולות המלבן החוסם כלפי פנים לפי ערכי הנקודה — וכך ברשת של $3 \times 3$ אנו גוזרים בקלות את הזרוע האופקית והזרוע האנכית של הצלב!

1. מנגנון הציור באמצעות drawOn: aCanvas ומקרה הבוחן CrossMorph

איך מציירים צלב המורכב משני מלבנים מוצלבים בעזרת חלוקה לרשת 3x3 ו-insetBy:?

כאשר אנחנו מעוניינים להגדיר רכיב בעל מראה חזותי ייחודי משלנו, אנחנו לא מרכיבים צורות קיימות אלא יוצרים תת-מחלקה של Morph ודורסים את מתודת הציור הבסיסית drawOn: aCanvas. בכל פעם שנדרש לצייר את הרכיב מחדש על המסך, סביבת Squeak שולחת אליו אוטומטית את ההודעה drawOn: aCanvas, כאשר הארגומנט aCanvas הוא עצם מסוג Canvas המספק שירותי ציור פרימיטיביים (ציור קווים, מלבנים, שקיפות וטקסט).

בוא נגדיר יחד מחלקה חדשה בשם CrossMorph (תת-מחלקה של Morph) המציירת צלב המורכב משני מלבנים מוצלבים — זרוע אופקית וזרוע אנכית:

Morph subclass: #CrossMorph
    instanceVariableNames: ''
    classVariableNames: ''
    poolDictionaries: ''
    category: 'Morphic-Demo'

איך נחשב את שני המלבנים המרכיבים את הצלב בתוך המתודה drawOn: aCanvas? נחלק את המלבן החוסם של הצורה (self bounds) לרשת דמיונית של $3 \times 3$ על ידי חישוב שליש הגובה (self height / 3.0) ושליש הרוחב (self width / 3.0). כעת נשתמש במתודה הנפלאה insetBy: של מחלקת Rectangle, שמקבלת נקודה ומחזירה מלבן חדש שהקואורדינטות של פינתו השמאלית-עליונה והימנית-תחתונה מוסטות כלפי פנים לפי ערכי הנקודה:

drawOn: aCanvas
    | crossHeight crossWidth horizontalBar verticalBar |
    crossHeight := self height / 3.0.
    crossWidth := self width / 3.0.
    horizontalBar := self bounds insetBy: 0 @ crossHeight.
    verticalBar := self bounds insetBy: crossWidth @ 0.
    aCanvas fillRectangle: horizontalBar color: self color.
    aCanvas fillRectangle: verticalBar color: self color
  • self bounds insetBy: 0 @ crossHeight: אינו מכווץ כלל את הרוחב (0 בציר ה-x), אך מכווץ שליש מלמעלה ושליש מלמטה בציר ה-y — וכך מתקבלת הזרוע האופקית (horizontalBar)!
  • self bounds insetBy: crossWidth @ 0: מכווץ שליש משמאל ושליש מימין בציר ה-x, ואינו מכווץ כלל את הגובה (0 בציר ה-y) — וכך מתקבלת הזרוע האנכית (verticalBar)!
  • לבסוף, אנו מציירים את שני המלבנים על גבי ה-Canvas באמצעות המתודה fillRectangle:color:.

2. זיהוי פגיעת עכבר מדויקת: דריסת המתודה containsPoint: aPoint

למה הצלב מגיב ללחיצות גם בפינות הריקות שלו, ואיך פותרים זאת בעזרת or:?

אם ניצור מופע של CrossMorph וננסה לתפוס אותו עם העכבר באחת מארבע הפינות הריקות שמסביב לזרועות הצלב, נגלה הפתעה: הצלב נתפס ומגיב ללחיצה גם באוויר הריק! מדוע זה קורה? משום שכברירת מחדל, מתודת containsPoint: במחלקה Morph בודקת רק אם נקודת העכבר נמצאת בתוך המלבן החוסם (self bounds).

כדי להבטיח שצורת הצלב תגיב אך ורק כאשר לוחצים על שטח הצלב עצמו (ולא על הפינות הריקות), אנחנו דורסים את המתודה containsPoint: aPoint ומחזירים חיבור לוגי (or:) הבודק האם הנקודה נמצאת בתוך הזרוע האופקית או בתוך הזרוע האנכית:

containsPoint: aPoint
    | crossHeight crossWidth horizontalBar verticalBar |
    crossHeight := self height / 3.0.
    crossWidth := self width / 3.0.
    horizontalBar := self bounds insetBy: 0 @ crossHeight.
    verticalBar := self bounds insetBy: crossWidth @ 0.
    ^ (horizontalBar containsPoint: aPoint) or: [verticalBar containsPoint: aPoint]

🛠️ שכתוב 1: מניעת כפילות קוד באמצעות מתודות עזר

שים לב שחישובי horizontalBar ו-verticalBar מופיעים פעמיים — גם ב-drawOn: וגם ב-containsPoint:. בהנדסת תוכנה נכונה אנו מרכזים את חישובי המלבנים במתודות עזר פרטיות (horizontalBar ו-verticalBar) כדי למנוע כפילות קוד!

🎨 שכתוב 2: מניעת ציור כפול של מרכז הצלב

כאשר מציירים את horizontalBar ועליו את verticalBar, הריבוע המרכזי המשותף לשני המלבנים מצויר פעמיים! (בעיה בולטת אם הצבע שקוף למחצה). ניתן לפצל את הזרוע האנכית לחלק עליון וחלק תחתון כך שהשטח המשותף יצויר פעם אחת בלבד.

עזר גרפי 17.4 • אנטומיית הגיאומטריה של CrossMorph

חלוקת המלבן החוסם לרשת 3x3, כיווץ עם insetBy: וסינון הפינות הריקות ב-containsPoint:

CrossMorph 3x3 Grid
✗ false ✗ false ✗ false ✗ false ✓ true self bounds (רשת 3x3) horizontalBar := self bounds insetBy: 0 @ crossHeight מכווץ שליש מלמעלה ושליש מלמטה (0 בציר X) ⬅️ הזרוע האופקית verticalBar := self bounds insetBy: crossWidth @ 0 ⬅️ הזרוע האנכית containsPoint: aPoint (סינון 4 הפינות הריקות) ^ (horizontalBar containsPoint: aPoint) or: [verticalBar containsPoint: aPoint] לחיצה בפינות (✗) מחזירה false ומתעלמת מהעכבר!
✂️

מיני-סימולציה 17.4: מפענח המתודה bounds insetBy: ב-CrossMorph

לחץ על כל שלב בחישוב הצלב
🎯
סימולציה אינטראקטיבית גדולה 17.4

מעבדת בדיקת הפגיעה (Hit-Test Lab): לחץ על ה-CrossMorph ועל הפינות הריקות!

לחץ על כל אחד מ-9 התאים של המלבן החוסם (3x3):
בחר מימוש של containsPoint::
לחץ על אחת הפינות הריקות או על זרועות הצלב משמאל כדי לראות מה מחזירה המתודה containsPoint: aPoint!
⚡

הגשר הנרטיבי: הצלב שלנו יודע לצייר את עצמו ולזהות פגיעת עכבר – עכשיו בוא נלמד אותו להגיב ללחיצות, למקלדת ולאנימציה!

זיהוי שהעכבר לחץ על הצלב (containsPoint:) הוא רק הצעד הראשון. איך גורמים לצלב לגדול בלחיצה שמאלית, לקטון בלחיצה ימנית, להחליף צבע כשהעכבר מרחף מעליו, לזוז עם חצי המקלדת ולהתנפנף באנימציה מחזורית? בוא נעבור לסעיף 17.5!

פרק 17: הסביבה הגרפית Morphic • סעיף 17.5 אינטראקציה חיה ואנימציה מחזורית

אירועי עכבר, אירועי מקלדת ואנימציה באמצעות מנגנון ה-Stepping

הטיפול הדו-שלבי באירועי עכבר (handlesMouse... ו-mouse...), חשיבות הפקודה הקריטית self changed, הטיפול התלת-שלבי באירועי מקלדת (פוקוס newKeyboardFocus: וקודי חצים 28–31), ופרוטוקול האנימציה (stepTime ו-step).

🖱️
🧠

מודל מנטלי ועיקרון מנחה: קודם מרימים יד ("אני מאזין!"), אחר כך מגיבים — ובסוף קוראים ל-self changed!

💡 עכבר ב-2 שלבים

מערכת Morphic לא שולחת אירועי עכבר לכל רכיב סתם כך. בשלב 1 הרכיב מצהיר שהוא מעוניין להאזין (למשל handlesMouseDown: ^ true), ובשלב 2 הוא מממש את מתודת התגובה (mouseDown: anEvent) וקורא ל-self changed!

⚖️ מקלדת ב-3 שלבים

כדי לקלוט מקשים: (1) בכניסת עכבר לוקחים פוקוס מקלדת (anEvent hand newKeyboardFocus: self); (2) מטפלים בהקשה ב-handleKeystroke: לפי keyValue; (3) ביציאת עכבר משחררים פוקוס עם newKeyboardFocus: nil.

🎯 אנימציית Stepping

בפרוטוקול stepping and presenter, המתודה stepTime קובעת את מרווח הזמן במילישניות בין פעימות, והמתודה step נקראת אוטומטית בכל פעימה כדי לבצע את צעד האנימציה!

1. אירועי עכבר (Mouse Events): מנגנון דו-שלבי ופקודת self changed

שלב ההכרזה (handlesMouse...) ושלב התגובה (mouseDown:, mouseEnter:, mouseLeave:)

הטיפול באירועי עכבר בסמולטוק מתבצע תמיד בשני שלבים מובנים:

  • שלב 1 – הכרזה: הרכיב מודיע למערכת שהוא מעוניין להאזין לקבוצת אירועים באמצעות דריסת מתודות המתחילות ב-handles... והחזרת ^ true:
    • handlesMouseDown: anEvent – מאזינה לאירועי לחיצה, שחרור ותזוזה של העכבר.
    • handlesMouseOver: anEvent – מאזינה לאירועי כניסה ויציאה של סמן העכבר משטח הצורה.
    • handlesMouseOverDragging: anEvent – מאזינה לאירועי גרירה פנימה והחוצה משטח הצורה.
  • שלב 2 – תגובה: מימוש מתודת הטיפול הייעודית לאירוע שהתרחש: mouseDown: anEvent (לחיצה), mouseUp: anEvent (שחרור), mouseMove: anEvent (תזוזה), mouseEnter: anEvent (כניסה לשטח הצורה) ו-mouseLeave: anEvent (יציאה משטח הצורה).

בוא נראה דוגמה מלאה ב-CrossMorph (כולל פתרון תרגילי ההרחבה): הגדלת הצלב בלחיצה שמאלית (הכפתור האדום – redButtonPressed), הקטנתו בלחיצה ימנית (הכפתור הצהוב – yellowButtonPressed), והחלפת צבעו לאדום בכניסת עכבר ולירוק ביציאת עכבר:

CrossMorph >> handlesMouseDown: anEvent
    ^ true

CrossMorph >> mouseDown: anEvent
    anEvent redButtonPressed
        ifTrue: [self extent: (self extent * 1.1)].
    anEvent yellowButtonPressed
        ifTrue: [self extent: (self extent * 0.9)].
    self changed
🚨 למה הפקודה self changed היא קריטית? כאשר אנחנו משנים את המצב הפנימי של הרכיב, שליחת ההודעה self changed מודיעה לסביבת Morphic שהרכיב השתנה ויש לתזמן שליחת drawOn: לצורך ציורו מחדש על המסך!

2. אירועי מקלדת (Keyboard Events): מנגנון תלת-שלבי ופוקוס המקלדת

איך מעבירים פוקוס מקלדת דרך anEvent hand, קולטים את מקשי החצים (28–31) ומשחררים פוקוס?

בניגוד לעכבר שתמיד מצביע על נקודה ספציפית במסך, הקשת מקלדת צריכה לדעת לאיזה רכיב על המסך יש כרגע "פוקוס מקלדת" (Keyboard Focus)! לכן טיפול במקלדת מתבצע בשלושה שלבים מובנים:

"שלב 1: העברת פוקוס המקלדת אל הרכיב כאשר העכבר נכנס לשטחו (וצביעה באדום)"
CrossMorph >> handlesMouseOver: anEvent
    ^ true

CrossMorph >> mouseEnter: anEvent
    self color: Color red.
    anEvent hand newKeyboardFocus: self

"שלב 2: טיפול בהקשת המקש (handleKeystroke:) לפי קוד המקש keyValue"
CrossMorph >> handleKeystroke: anEvent
    | keyValue |
    keyValue := anEvent keyValue.
    keyValue = 30 ifTrue: [self position: self position - (0 @ 1)]. "חץ למעלה"
    keyValue = 31 ifTrue: [self position: self position + (0 @ 1)]. "חץ למטה"
    keyValue = 29 ifTrue: [self position: self position + (1 @ 0)]. "חץ ימינה"
    keyValue = 28 ifTrue: [self position: self position - (1 @ 0)] "חץ שמאלה"

"שלב 3: שחרור פוקוס המקלדת כאשר העכבר יוצא משטח הצורה (וצביעה בירוק)"
CrossMorph >> mouseLeave: anEvent
    self color: Color green.
    anEvent hand newKeyboardFocus: nil

שים לב: ההודעה anEvent hand שולפת מתוך האירוע את העצם המייצג את מצביע העכבר הפעיל (HandMorph), וההודעה newKeyboardFocus: self מורה לו להעביר את קליטת המקשים אל הרכיב שלנו (או לבטל את הפוקוס באמצעות העברת nil ביציאה).

3. אנימציה באמצעות מנגנון ה-Stepping (פרוטוקול stepping and presenter)

איך יוצרים תנועה רציפה ומחזורית בעזרת stepTime, step ומתודות הבקרה?

סביבת Morphic כוללת תשתית מובנית לביצוע אנימציות מחזוריות הרצות תחת הפרוטוקול stepping and presenter (ציור מחדש בסדרה של "צעדים"; קצב של 15–30 מילישניות יוצר תחושת רצף חלקה לעין, או למשל 100 מילישניות עבור 10 צעדים בשנייה):

stepTime

מחזירה מספר שלם המציין את מרווח הזמן המינימלי במילישניות בין צעד אנימציה אחד למשנהו (למשל ^ 100).

step

המתודה הנקראת אוטומטית על ידי המערכת בכל פעימת stepTime, ובה מבוצע שינוי המיקום, הזווית או הצבע של הצורה.

startStepping / stopStepping / isStepping

מתודות בקרה המפעילות את מנגנון הצעדים בעולם, עוצרות את האנימציה, או מחזירות בוליאני האם האנימציה פעילה כעת.

עזר גרפי 17.5 • מחזור חיי האירועים והאנימציה ב-Morphic

אירועי עכבר (2 שלבים), אירועי מקלדת (3 שלבים) ומנגנון ה-Stepping

Events & Stepping
1. אירועי עכבר (2 שלבים) שלב 1: handlesMouseDown: ^ true (או handlesMouseOver: / Dragging:) שלב 2: mouseDown: anEvent redButtonPressed / yellowButtonPressed + שליחת self changed לציור מחדש! 2. אירועי מקלדת (3 שלבים) 1. mouseEnter: newKeyboardFocus: self 2. handleKeystroke: (keyValue) 28=שמאל, 29=ימין, 30=מעלה, 31=מטה 3. mouseLeave: newKeyboardFocus: nil 3. אנימציית Stepping stepTime (^ 100 ms) קובעת תדירות פעימות האנימציה step (מתבצעת בכל פעימה) startStepping / stopStepping isStepping (בדיקת מצב פעיל)
⌨️

מיני-סימולציה 17.5: מפענח קודי מקשים (keyValue) וכפתורי העכבר ב-Squeak

לחץ על כל אירוע כדי לראות את שורת הקוד המתאימה
⚡
סימולציה אינטראקטיבית גדולה 17.5

מעבדת האירועים החיה של CrossMorph: עכבר, מקלדת (28–31) ואנימציית step

KeyboardFocus: nil (רחף על הצלב!) isStepping: false
רחף עם העכבר על הצלב (אדום + פוקוס מקלדת) וצא ממנו (ירוק + שחרור פוקוס)!
שלח אירועי עכבר, מקלדת (חצים) ואנימציית Stepping:
[Event Log] CrossMorph ready. Hover or click the cross!
⚡

הגשר הנרטיבי: שלטנו באירועי עכבר ומקלדת על צורות – אבל איך מבקשים מהמשתמש להקליד טקסט או לבחור פריט מתפריט קופץ?

לפעמים אנחנו לא רוצים לקלוט מקש-מקש בעצמנו, אלא להקפיץ למשתמש תיבת דו-שיח מוכנה להקלדת שם (FillInTheBlank) או תפריט קופץ תלוי-הקשר עם קווי הפרדה (PopUpMenu). איך מפעילים אותם בשורת קוד אחת ב-Squeak ומה הם מחזירים? בוא נעבור לסעיף 17.6!

פרק 17: הסביבה הגרפית Morphic • סעיף 17.6 ממשקי אינטראקציה מהירים ותפריטים

תיבת דו-שיח (FillInTheBlank) ותפריט קופץ תלוי-הקשר (PopUpMenu)

שימוש במחלקות המוכנות של Squeak לקבלת קלט טקסטואלי מהמשתמש (request:initialAnswer:) ולהצגת תפריט בחירה צף עם קווי הפרדה (labelArray:lines: ו-startUpWithCaption:).

💬
🧠

מודל מנטלי ועיקרון מנחה: דיאלוג מיידי עם המשתמש — שולחים הודעה למחלקת השירות ומקבלים תשובה מוכנה!

💡 האנלוגיה המוחשית

במקום לבנות מאפס טופס קלט עם שדה טקסט וכפתורי אישור/ביטול בכל פעם שאנחנו צריכים לשאול את המשתמש שאלה קצרה, סביבת Squeak מעמידה לרשותנו "שליחים מוכנים" — אחד לשאלה פתוחה (FillInTheBlank) ואחד לבחירה מרשימה (PopUpMenu)!

⚖️ חוק הברזל הפדגוגי

הכר היטב את הערכים המוחזרים: FillInTheBlank מחזירה את מחרוזת הטקסט שהוקלדה (או מחרוזת ריקה '' בביטול), ואילו PopUpMenu מחזירה את האינדקס המספרי של הפריט שנבחר (1, 2, 3... או 0 בביטול)!

🎯 תובנת המפתח

ב-PopUpMenu labelArray:lines: המערך הראשון קובע את שמות הפריטים בתפריט, והמערך השני מציין את מספרי האינדקסים של הפריטים שאחריהם יימתח קו הפרדה חזותי!

1. תיבת דו-שיח לקלט טקסטואלי: המחלקה FillInTheBlank

איך מציגים חלונית דו-שיח מודאלית ומקבלים מהמשתמש מחרוזת טקסט?

כאשר התוכנית שלנו זקוקה לקלט טקסטואלי מהיר מהמשתמש (למשל שם שחקן, שם קובץ או פרמטר טקסטואלי), איננו צריכים לבנות חלון מורכב בעצמנו. סביבת Squeak מעמידה לרשותנו את המחלקה FillInTheBlank, המציגה חלונית דו-שיח מודאלית שבה המשתמש יכול להקליד טקסט.

ההפעלה מתבצעת באמצעות מתודת המחלקה המרכזית request:initialAnswer:, המקבלת שתי מחרוזות:

FillInTheBlank request: 'enter your name:' initialAnswer: '??????'
  • request: – המחרוזת הראשונה, המוצגת בכותרת/גוף התיבה כהנחיה למשתמש (בדוגמה: 'enter your name:').
  • initialAnswer: – המחרוזת השנייה, המוצגת כערך ברירת המחדל בתוך שדה הטקסט (בדוגמה: '??????'), והיא גם הערך שיוחזר אם המשתמש ילחץ אישור מבלי לשנות דבר.
  • הערך המוחזר: מחרוזת הטקסט שהקליד המשתמש, או מחרוזת ריקה ('') אם המשתמש ביטל את הפעולה (Cancel).

2. תפריט קופץ תלוי-הקשר: המחלקה PopUpMenu

אתחול תפריט עם קווי הפרדה (labelArray:lines:) והצגתו במיקום העכבר (startUpWithCaption:)

כאשר אנחנו רוצים להציג למשתמש תפריט בחירה צף על גבי המסך (תפריט תלוי-הקשר), אנחנו משתמשים במחלקה PopUpMenu בשני צעדים ברורים:

menu := PopUpMenu labelArray: #('x' 'y' 'z') lines: #(1 2 3).
choice := menu startUpWithCaption: 'Choose an item'.

📋 צעד 1: יצירת התפריט (labelArray:lines:)

מתודת המחלקה מקבלת שני מערכים:
1. המערך הראשון (labelArray:): שמות הפריטים שיופיעו בתפריט, למשל #('x' 'y' 'z').
2. המערך השני (lines:): מספרי האינדקסים של הפריטים שאחריהם יימתח קו הפרדה חזותי, למשל #(1 2 3).

🖱️ צעד 2: הצגה וקבלת הבחירה (startUpWithCaption:)

ההודעה startUpWithCaption: 'Choose an item' פותחת את התפריט במיקום סמן העכבר עם הכותרת המבוקשת וממתינה לבחירה.
הערך המוחזר: מספר שלם המציין את האינדקס של הפריט שנבחר (1 עבור 'x', 2 עבור 'y', 3 עבור 'z'), או 0 אם המשתמש לחץ מחוץ לתפריט וביטל!

עזר גרפי 17.6 • אנטומיית פקדי הדו-שיח ב-Squeak

השוואה ויזואלית בין FillInTheBlank לבין PopUpMenu

Dialogs & Menus
request: 'enter your name:' ?????? initialAnswer: Accept (→ String) Cancel (→ '') FillInTheBlank (מחזיר מחרוזת) Caption: 'Choose an item' x → מחזיר 1 y → מחזיר 2 z → מחזיר 3 PopUpMenu (לחיצה בחוץ מחזירה 0!)
🔮

מיני-סימולציה 17.6: מפענח הערכים המוחזרים מ-FillInTheBlank ומ-PopUpMenu

לחץ על כל תרחיש משתמש כדי לראות מה יוחזר לקוד
🖥️
סימולציה אינטראקטיבית גדולה 17.6

מעבדת הממשקים של Squeak: הרץ בלייב את FillInTheBlank ואת PopUpMenu!

בחר איזה קטע קוד להריץ ב-Squeak:
[Return Value] Run one of the expressions above!
enter your name:
⚡

הגשר הנרטיבי: למדנו לתקשר עם המשתמש בתיבות דו-שיח ובתפריטים – עכשיו איך גורמים לצורות לתקשר זו עם זו בגרירה והשמטה (Drag & Drop)?

אחת הפעולות הטבעיות ביותר בממשק גרפי היא לתפוס צורה אחת עם העכבר, לגרור אותה ולהשמיט אותה לתוך צורה אחרת (Drag and Drop). איך העצם המקבל מחליט אם להסכים לקבל את העצם הנגרר (למשל רק אם הוא כחול!), ואיך גורמים לעצם שנדחה "לזנק" בחזרה למקומו המקורי? בוא נעבור לסעיף 17.7!

פרק 17: הסביבה הגרפית Morphic • סעיף 17.7 Drag and Drop Protocol

מנגנון גרירה והשמטה (Drag and Drop): העצם המקבל והעצם הנגרר

שיתוף הפעולה בין העצם הנגרר (DroppedMorph) לעצם המקבל (ReceiverMorph): המתודות wantsDroppedMorph:event:, repelsMorph:event:, wantsToBeDroppedInto: ו-rejectDropMorphEvent:.

📥
🧠

מודל מנטלי ועיקרון מנחה: לטנגו של גרירה והשמטה דרושים שניים — גם המקבל וגם הנגרר צריכים להסכים!

💡 צד העצם המקבל (Receiver)

העצם המקבל נשאל ב-wantsDroppedMorph:event: האם הוא מסכים לקלוט את העצם הנגרר. אם הוא מסרב ו-repelsMorph:event: מחזירה true, העצם שנדחה "מועף" בזינוק חזרה למקומו המקורי!

⚖️ צד העצם הנגרר (Dropped)

גם לעצם הנגרר יש זכות החלטה! המתודה wantsToBeDroppedInto: aMorph מאפשרת לו לקבוע לתוך אילו מכולות הוא מוכן להישמט, ו-rejectDropMorphEvent: קובעת מה יעשה אם נדחה!

🎯 תובנת המפתח

כברירת מחדל ב-Morph, עצם שנדחה פשוט נשאר מונח בנקודת העזיבה מעל המקבל. כדי לגרום לו לחזור למקומו המקורי בעת דחייה, אנו מממשים ב-repelsMorph:event: את שלילת הקבלה: ^ (self wantsDroppedMorph: aMorph event: anEvent) not!

1. שני השחקנים בגרירה והשמטה ומתודות העצם המקבל (ReceiverMorph)

איך בודקים תנאי קבלה (wantsDroppedMorph:event:) ואיך מעיפים עצם שנדחה חזרה למקומו (repelsMorph:event:)?

בכל פעולת גרירה והשמטה (Drag and Drop) ב-Morphic מעורבים תמיד שני עצמים: (1) העצם הנגרר (DroppedMorph), ו-(2) העצם המקבל (ReceiverMorph) שמעליו המשתמש מרפה מהעצם הנגרר.

בוא נבחן תחילה את שתי המתודות המרכזיות בצד של העצם המקבל (The Receiver):

  • wantsDroppedMorph: aMorph event: anEvent: נקראת אוטומטית כאשר המשתמש מרפה מהעצם הנגרר מעל שטח המקבל, ומחזירה ערך בוליאני הקובע האם המקבל מסכים לקלוט את העצם ולהכילו. ברירת המחדל במחלקה Morph מחזירה תמיד true. לדוגמה, אם נרצה שהמקבל יקלוט אך ורק עצמים שצבעם כחול, נדרוס את המתודה כך:
ReceiverMorph >> wantsDroppedMorph: aMorph event: anEvent
    ^ aMorph color = Color blue
  • repelsMorph: aMorph event: anEvent: קובעת מה קורה אם העצם הנגרר נדחה. כברירת מחדל, עצם שנדחה פשוט נשאר מונח במקום העזיבה ללא הכלה במקבל. אם נרצה שהמקבל ידחה את העצם באופן אקטיבי כך שהעצם הנגרר שנדחה "יועף" חזרה בזינוק למקומו המקורי על המסך, עלינו להחזיר true. המימוש התקני מבוסס על שלילת מתודת הקבלה:
ReceiverMorph >> repelsMorph: aMorph event: anEvent
    ^ (self wantsDroppedMorph: aMorph event: anEvent) not

2. מתודות העצם הנגרר (DroppedMorph) והתרגיל ההדדי המסכם

wantsToBeDroppedInto:, rejectDropMorphEvent: ותרגיל החלפת הצבע בעת דחייה

גם לעצם הנגרר עצמו יש שתי מתודות ייעודיות המאפשרות לו לשלוט בהתנהגותו:

  • wantsToBeDroppedInto: aMorph: מקבלת את העצם המקבל ומאפשרת לעצם הנגרר לקבוע בעצמו לתוך אילו מכולות או עצמים מקבלים הוא מוכן להישמט (ברירת מחדל: ^ true).
  • rejectDropMorphEvent: anEvent: מגדירה את התנהגות העצם הנגרר במקרה שבו המקבל דחה אותו (למשל השמעת צליל, שינוי צבע או אנימציית סירוב, לצד חזרתו למקומו המקורי).

🧩 תרגיל מסכם (תרגיל 17.7): שיתוף פעולה הדדי בין ReceiverMorph ל-DroppedMorph

נגדיר מחלקת ReceiverMorph שמקבלת רק עצמים שצבעם שונה מצבעה שלה, ולצידה מחלקת DroppedMorph שכאשר היא נדחית — היא חוזרת למקומה המקורי ומשנה את צבעה כך שבגרירה הבאה היא תתקבל בהצלחה!

"במחלקת העצם המקבל - מקבל רק עצמים שצבעם שונה מצבעו:"
ReceiverMorph >> wantsDroppedMorph: aMorph event: anEvent
    ^ (aMorph color = self color) not

ReceiverMorph >> repelsMorph: aMorph event: anEvent
    ^ (self wantsDroppedMorph: aMorph event: anEvent) not

"במחלקת העצם הנגרר - בעת דחייה משנה צבע וחוזר למקומו:"
DroppedMorph >> rejectDropMorphEvent: anEvent
    super rejectDropMorphEvent: anEvent.
    self color: Color random
עזר גרפי 17.7 • תרשים זרימת ההחלטות ב-Drag and Drop

מהרגע שבו מרפים מהעצם הנגרר מעל העצם המקבל ועד לקליטה או לזינוק חזרה

Drop Decision Flow
1. הנגרר: wantsToBeDroppedInto: האם הנגרר מוכן להיכנס למקבל? ← 2. המקבל: wantsDroppedMorph:event: האם הצבע/התנאי מתאים לקבלה? ✓ אם true: קליטה והכלה! העצם נכנס לתוך ה-submorphs של המקבל ✗ אם false: דחייה! בדיקת repelsMorph:event: (זינוק חזרה) + הפעלת rejectDropMorphEvent:
🧭

מיני-סימולציה 17.7: סייר 4 מתודות ה-Drag and Drop

לחץ על כל מתודה כדי לראות למי היא שייכת ומה תפקידה
🧲
סימולציה אינטראקטיבית גדולה 17.7

מכונת ה-Drag & Drop: בדוק קבלה, דחייה וזינוק חזרה (repelsMorph) ותרגיל שינוי הצבע!

Rule: Receiver accepts ONLY Blue (aMorph color = Color blue)
Click a DroppedMorph to drop it:
ReceiverMorph Accepted submorphs: 0
בחר מצב חוקי קבלה ב-ReceiverMorph:
Click Blue, Red, or Green to drop it onto ReceiverMorph!
⚡

הגשר הנרטיבי: עכשיו יש לנו את כל הכלים לבנות משחק שלם – בוא נבנה יחד את קוביית המשחק החיה DieMorph!

למדנו לצייר על Canvas, להגיב ללחיצות עכבר, להריץ אנימציית step ולשלוח הודעות. בסעיף 17.8 נחבר את כל היכולות האלו לפרויקט מופתי אחד: פיתוח המחלקה DieMorph — קוביית משחק אינטראקטיבית (עם 1 עד 9 פאות!) שמתגלגלת באנימציה חיה, מציירת את הנקודות שלה באופן דינמי בעזרת perform:, ונעצרת או ממשיכה בלחיצת עכבר!

פרק 17: הסביבה הגרפית Morphic • סעיף 17.8 פרויקט מסכם: קוביית משחק חיה

דוגמה מלאה: קוביית משחק אינטראקטיבית (DieMorph) והפעלה דינמית עם perform:

פיתוח רכיב Morphic שלם מ-א' ועד ת': ירושה מ-BorderedMorph, משתני המופע faces dieValue isStopped, קואורדינטות יחסיות ב-face1..face9, קריאה דינמית למתודה עם perform:, ואנימציית גלגול הנשלטת בלחיצת עכבר.

🎲
🧠

מודל מנטלי ועיקרון מנחה: קואורדינטות יחסיות (0..1) + שליחת הודעה דינמית (perform:) = קוד נקי וללא תנאי if!

💡 קואורדינטות יחסיות (0@0 עד 1@1)

במקום לקבע מיקומי נקודות בפיקסלים קשיחים (שיישברו ברגע שנגדיל את הקובייה), מתודות face1 עד face9 מחזירות נקודות יחסיות בין 0@0 ל-1@1 (למשל 0.5@0.5 למרכז הקובייה)!

⚖️ הקסם של perform:

במקום לכתוב סדרת תנאי if ארוכה עבור כל ערך של dieValue, המתודה drawOn: מרכיבה סימבול בזמן ריצה (('face', dieValue asString) asSymbol → #face3) ומפעילה אותו דינמית עם self perform:!

🎯 שילוב step + mouseDown:

בכל 100 מילישניות (stepTime ^ 100) מתודת step מגרילה ערך אקראי כל עוד isStopped הוא false. לחיצה שמאלית (redButtonPressed) הופכת את isStopped := isStopped not ועוצרת או מפעילה את הקובייה!

1. הגדרת המחלקה DieMorph, אתחול ומתודות הגישה

ירושה מ-BorderedMorph, משתני המופע faces, dieValue, isStopped ואימות קלט

עכשיו אנחנו מחברים את כל מה שלמדנו בפרק הזה ליצירת רכיב Morphic שלם ומרשים: קוביית משחק אינטראקטיבית (DieMorph) התומכת ב-1 עד 9 פאות, מציירת את ערכה באמצעות עיגולים שחורים על רקע ירוק, ומבצעת אנימציית גלגול שנעצרת ומתחדשת בלחיצת עכבר.

נגדיר את DieMorph כתת-מחלקה של BorderedMorph (כדי שהקובייה תהנה ממסגרת מובנית), עם שלושה משתני מופע: faces (מספר הפאות הכולל, 1 עד 9, ברירת מחדל 6), dieValue (הערך הנוכחי הפונה מעלה), ו-isStopped (משתנה בוליאני המציין האם אנימציית הגלגול נעצרה או פועלת):

BorderedMorph subclass: #DieMorph
    instanceVariableNames: 'faces dieValue isStopped'
    classVariableNames: ''
    poolDictionaries: ''
    category: 'SBE-Morphic'

"מתודת מחלקה ליצירת קובייה עם מספר פאות מבוקש:"
DieMorph class >> faces: aNumber
    ^ self new faces: aNumber

"מתודת האתחול ומתודות הגישה והעדכון:"
DieMorph >> initialize
    super initialize.
    self extent: 50 @ 50.
    self color: Color green.
    dieValue := 1.
    faces := 6.
    isStopped := false

DieMorph >> faces: aNumber
    (aNumber isInteger and: [ aNumber > 0 ] and: [ aNumber <= 9 ])
        ifTrue: [ faces := aNumber ]

DieMorph >> dieValue: aNumber
    (aNumber isInteger and: [ aNumber > 0 ] and: [ aNumber <= faces ])
        ifTrue: [
            dieValue := aNumber.
            self changed ]

שים לב שבמתודה dieValue: aNumber, מיד לאחר עדכון המשתנה dieValue, אנו קוראים לפקודה הקריטית self changed כדי להודיע למערכת לצייר את הקובייה מחדש עם הערך המעודכן!

2. מיפוי הנקודות (face1 עד face9), הפעלה דינמית עם perform: וציור הנקודות

איך מציירים כל פאה בקובייה ללא משפטי if ובכל גודל קובייה שנבחר?

לכל פאה (face1 עד face9) מוגדרת מתודה המחזירה מערך דינמי ({ ... }) של נקודות יחסיות (בין 0@0 ל-1@1) המציינות את מיקומי העיגולים על פני הקובייה. לדוגמה:

DieMorph >> face1
    ^ { 0.5 @ 0.5 }

DieMorph >> face2
    ^ { 0.25 @ 0.25 . 0.75 @ 0.75 }

DieMorph >> face3
    ^ { 0.25 @ 0.25 . 0.75 @ 0.75 . 0.5 @ 0.5 }

ועכשיו תסתכל על היופי של המתודה drawOn: aCanvas ומתודת העזר drawDotOn: aCanvas at: aPoint:

DieMorph >> drawOn: aCanvas
    super drawOn: aCanvas.
    (self perform: ('face' , dieValue asString) asSymbol)
        do: [ :aPoint | self drawDotOn: aCanvas at: aPoint ]

DieMorph >> drawDotOn: aCanvas at: aPoint
    aCanvas
        fillOval: (Rectangle
            center: self position + (self extent * aPoint)
            extent: self extent / 6)
        color: Color black
  • super drawOn: aCanvas: מבקשת ממחלקת האב (BorderedMorph) לצייר תחילה את המלבן הירוק ואת המסגרת של הקובייה!
  • הפעלת מתודה דינמית באמצעות perform:: במקום סדרת תנאי if ארוכה, המתודה משרשרת את המחרוזת 'face' עם dieValue asString (למשל 'face3'), ממירה אותה לסימבול (#face3), ושולחת לעצמה את ההודעה באמצעות self perform: #face3!
  • drawDotOn: aCanvas at: aPoint: מתרגמת כל נקודה יחסית aPoint לקואורדינטת מסך אמיתית לפי self position + (self extent * aPoint), קובעת את גודל העיגול כ-self extent / 6, ומציירת עיגול שחור באמצעות fillOval:color:!

3. אנימציית הגלגול ושליטה בעכבר (step ו-mouseDown:)

הגרלת פאה אקראית בכל 100 מילישניות ועצירה/הפעלה בלחיצה שמאלית

DieMorph >> stepTime
    ^ 100

DieMorph >> step
    isStopped ifFalse: [ self dieValue: (1 to: faces) atRandom ]

DieMorph >> handlesMouseDown: anEvent
    ^ true

DieMorph >> mouseDown: anEvent
    anEvent redButtonPressed
        ifTrue: [ isStopped := isStopped not ]
עזר גרפי 17.8 • מנגנון הציור הדינמי של DieMorph

איך פעימת step, שליחת perform: ומיפוי נקודות יחסיות בונים את הקובייה החיה

Dynamic perform: Pipeline
1. step (כל 100ms) (1 to: faces) atRandom self dieValue: 3 ↓ self changed → 2. drawOn: aCanvas ('face', dieValue asString) asSymbol self perform: #face3 {0.25@0.25 . 0.75@0.75 . 0.5@0.5} → 3. drawDotOn:at: (fillOval) pos + (extent * aPoint) dot size: extent / 6
⚙️

מיני-סימולציה 17.8: מפענח הקריאה הדינמית self perform: עבור פאות 1 עד 9

בחר ערך dieValue כדי לראות את הסימבול ואת מערך הנקודות
🎲
סימולציה אינטראקטיבית גדולה 17.8

הקובייה החיה (Live DieMorph Viewer): לחץ על הקובייה לעצירה/הפעלה ושנה את faces:!

לחץ קליק שמאלי על הקובייה כדי להפוך את isStopped:
🟢 מתגלגלת! (לחץ על הקובייה כדי לעצור)
📊 לוח שעונים חי של משתני המופע ב-DieMorph:
faces
6
dieValue
3
isStopped
false
שנה את מספר הפאות (aDie faces: aNumber בין 1 ל-9):
[drawOn:] self perform: #face3 → drawing 3 dots
⚡

הגשר הנרטיבי: ראינו את Canvas מצייר מלבנים ועיגולים ב-CrossMorph וב-DieMorph – אילו עוד יכולות מתקדמות מסתתרות במחלקת Canvas?

בשני מקרי הבוחן שלנו נעזרנו במתודות fillRectangle:color: ו-fillOval:color: של העצם aCanvas. בסעיף 17.9 נכיר לעומק את מלוא היכולות של מחלקת Canvas (קווים, מצולעים, גופנים, תמונות ביטמפ Form, טרנספורמציות Scaling ורוטציה, שקיפות והצללות), יחד עם טעינת קובצי תמונה חיצוניים ואנימציה מבוססת תהליך נפרד (fork)!

פרק 17: הסביבה הגרפית Morphic • סעיף 17.9 + הרחבות מנוע הציור הגרפי, תמונות ותהליכים

מחלקת Canvas, טעינת תמונות חיצוניות (Form) ואנימציה מבוססת תהליך (fork)

הכרת מנוע הציור הגרפי של Morphic (קווים, מלבנים, מצולעים, גופנים, תמונות ביטמפ Form, Scaling, רוטציה, הצללות ושקיפות), טעינת תמונות עם ImageReadWriter והקטנתן עם magnifyBy:, והרצת אנימציה כ-Thread נפרד באמצעות fork.

🖼️
🧠

מודל מנטלי ועיקרון מנחה: Canvas הוא מנוע הציור האוניברסלי — מצורות וקטוריות ועד תמונות וטרנספורמציות!

💡 יכולות הציור של Canvas

המחלקה Canvas מספקת מתודות לציור צורות גיאומטריות (קווים, מלבנים, עיגולים, מצולעים), טקסט וגופנים, הצגת תמונות ביטמפ (Form) וביצוע טרנספורמציות של שינוי קנה-מידה (Scaling) וסיבוב (Rotation).

⚖️ תמונות חיצוניות (ImageReadWriter)

ניתן לטעון קובץ תמונה חיצוני אל עצם תמונה (Form) באמצעות ImageReadWriter formFromFileNamed: 'image.png' ולשנות את קנה המידה שלו בקלות באמצעות form magnifyBy: 0.5!

🎯 אנימציה עם Thread נפרד (fork)

מלבד מנגנון ה-step המובנה, ניתן להריץ אנימציות ברקע באמצעות בלוק המופעל כ-Thread עצמאי בעזרת ההודעה fork והשהיה מחזורית עם (Delay forMilliseconds: 100) wait!

1. סעיף 17.9: מחלקת Canvas ויכולותיה הגרפיות המתקדמות

מנוע הציור הגרפי הבסיסי ותת-המחלקות המתקדמות שלו בסביבת Morphic

בכל פעם שמימשנו את המתודה drawOn: aCanvas, קיבלנו לידינו מופע של המחלקה Canvas. מהו בדיוק תפקידה של מחלקה זו? המחלקה Canvas מספקת את מנוע הציור הגרפי הבסיסי עבור כל סביבת Morphic, והיא מעמידה לרשותנו ארגז כלים עשיר במיוחד:

📐 1. צורות גיאומטריות, טקסט ותמונות ביטמפ

• צורות גיאומטריות: ציור קווים (line:to:width:color:), מלבנים (fillRectangle:color:), אליפסות/עיגולים (fillOval:color:) ומצולעים.
• טקסט וגופנים: ציור מחרוזות וטקסט מעוצב בעזרת גופנים (Fonts) שונים.
• תמונות ביטמפ: הצגת תמונות מבוססות פיקסלים המיוצגות על ידי המחלקה Form.

✨ 2. טרנספורמציות ותת-מחלקות Canvas מתקדמות

• טרנספורמציות גיאומטריות: תמיכה בשינוי קנה-מידה (Scaling) וברוטציה/סיבוב (Rotation).
• סוגי Canvas מתקדמים: קיימות תת-מחלקות ייעודיות של Canvas התומכות בהצללות (Shadows), שקיפות (Alpha blending), צבעים מדורגים (Gradients) וציור מואץ.

2. טעינת תמונות חיצוניות ואנימציה מבוססת תהליך נפרד (Thread / fork)

שימוש ב-ImageReadWriter ו-magnifyBy:, והרצת לולאת אנימציה ברקע עם Delay ו-fork

לצד כלי הציור של Canvas, בוא נכיר שתי טכניקות שימושיות מאוד לעבודה מעשית בסביבת Squeak:

טכניקה 1 · טעינת תמונות חיצוניות

טעינת קובץ תמונה ל-Form והקטנתו ב-50%

באמצעות המחלקה ImageReadWriter אנו טוענים קובץ תמונה חיצוני (כגון 'image.png') אל עצם מסוג Form, ובעזרת ההודעה magnifyBy: 0.5 אנו מקטינים את ממדיו ב-50%:

form := ImageReadWriter formFromFileNamed: 'image.png'.
form := form magnifyBy: 0.5.
טכניקה 2 · אנימציית Thread / Fork

אנימציה ברקע באמצעות בלוק ו-fork

מלבד מנגנון ה-step של Morphic, ניתן להריץ אנימציות ברקע באמצעות בלוק המופעל כתהליך נפרד (Thread) ע"י שליחת ההודעה fork לבלוק ושימוש ב-Delay להשהיה בין צעדים:

[ 1000 timesRepeat: [
    (Delay forMilliseconds: 100) wait.
    self doSomething ] ] fork
עזר גרפי 17.9 • ארכיטקטורת מחלקת Canvas ושני מסלולי האנימציה

שירותי הציור של Canvas והשוואת מנגנון ה-Stepping מול תהליך רקע (fork)

Canvas & Concurrency
1. יכולות מחלקת Canvas ותת-מחלקותיה צורות גיאומטריות וטקסט קווים, מלבנים, מצולעים, גופנים תמונות ביטמפ (Form) ImageReadWriter, magnifyBy: טרנספורמציות ויכולות מתקדמות Scaling, רוטציה, הצללות (Shadows), שקיפות (Alpha) ו-Gradients 2. שתי דרכים לביצוע אנימציה ב-Squeak א. מנגנון ה-Stepping המובנה ב-Morphic דריסת stepTime ו-step (נשלט ע"י startStepping / stopStepping) ב. אנימציה מבוססת תהליך נפרד (Thread / fork) [ 1000 timesRepeat: [ (Delay forMilliseconds: 100) wait... ] ] fork
🎨

מיני-סימולציה 17.9: סייר יכולות Canvas, תמונות Form ותהליכי fork

לחץ על כל יכולת להסבר ולקוד
✨
סימולציה אינטראקטיבית גדולה 17.9

סטודיו ה-Canvas החי: Scaling, רוטציה, שקיפות, הצללות, magnifyBy: ואנימציית fork

🖼️ Form ('image.png')
הפעל יכולות של Canvas, Form ו-fork:
[Canvas Studio] Select a transformation or fork animation!
⚡

הגשר הנרטיבי: השלמנו את כל פרקי התוכן של פרק 17 – בוא נעבור לסיכום האינטגרטיבי ולכרטיסיות הזיכרון התלת-ממדיות!

בלשונית האחרונה והמסכמת של פרק 17 (סעיף 17.10) נאגד את כל עקרונות הסביבה הגרפית Morphic לתמונה הנדסית שלמה, נתרגל במילון המושגים האינטראקטיבי בכרטיסיות תלת-ממד (3D Flashcards), ונפתח צוהר אל פרק 18 (המושג "מטה-מחלקה")!

פרק 17: הסביבה הגרפית Morphic • סעיף 17.10 אינטגרציה ומילון מושגים

סיכום אינטגרטיבי של פרק 17 ומילון מושגים בכרטיסיות תלת-ממד (3D Flashcards)

ריכוז כל היכולות הגרפיות של Morphic שלמדנו בפרק: מ-asMorph ו-openInWorld, דרך הרכבת צורות, דריסת drawOn: ו-containsPoint:, אירועי עכבר ומקלדת, אנימציה, FillInTheBlank ו-PopUpMenu, גרירה והשמטה ועד ל-DieMorph.

🏆
🧠

מודל מנטלי ועיקרון מנחה: כל רכיב גרפי ב-Morphic הוא עצם שלם המשלב מראה, מבנה היררכי, תגובה לאירועים וחיוּת!

💡 אנטומיה והכלה (17.1–17.3)

ישירות וחיות, הילת ה-Halo וניתוק פריטים עם ה-pickup, המרת עצמים עם asMorph והצגה ב-openInWorld, מערכת הצירים (0@0 למעלה-משמאל, y כלפי מטה) והרכבת עץ תת-רכיבים עם addMorph: ו-removeMorph:.

⚖️ ציור ואינטראקציה (17.4–17.7)

יצירת צורות בדריסת drawOn: aCanvas ו-containsPoint:, אירועי עכבר ב-2 שלבים (+ self changed), מקלדת ב-3 שלבים (newKeyboardFocus:), תיבות דו-שיח ו-PopUp, ומנגנון Drag & Drop דו-צדדי.

🎯 אנימציה ודינמיות (17.8–17.10)

אנימציה באמצעות מנגנון ה-Stepping (stepTime ו-step) או תהליך נפרד (fork), טעינת תמונות חיצוניות (ImageReadWriter ו-magnifyBy:), ושימוש ב-perform: ב-DieMorph!

1. ששת עמודי התווך המסכמים של פרק 17

ריכוז מסודר של כל העקרונות והיכולות שלמדנו בסביבה הגרפית Morphic

1. הפיכת כל עצם לרכיב גרפי חי

שימוש בהודעה asMorph להמרת מחרוזות וצבעים לרכיבי Morph, הצגתם בעולם באמצעות openInWorld, וחקירתם החיה דרך openViewerForArgument וה-Morphic Halo.

2. היררכיית Morph, מערכת הצירים ויחסי הכלה

שליטה במיקום (position:), ממדים (extent:), מרכוז (center:) וצבע (color:) במערכת שבה (0,0) בפינה השמאלית-העליונה, והרכבת עץ אב-בן באמצעות addMorph: ו-removeMorph:.

3. יצירת צורות מותאמות אישית (CrossMorph)

דריסת מתודת הציור drawOn: aCanvas (תוך שימוש ב-bounds insetBy:) ודריסת מתודת זיהוי הפגיעה containsPoint: aPoint לסינון הפינות הריקות של המלבן החוסם.

4. טיפול באירועי עכבר ומקלדת בשלבים

הכרזה ב-handlesMouse... ותגובה ב-mouseDown: / mouseEnter: / mouseLeave: עם קריאה ל-self changed; העברת פוקוס מקלדת עם anEvent hand newKeyboardFocus: וטיפול ב-handleKeystroke:.

5. פקדי דו-שיח, גרירה והשמטה ו-DieMorph

שימוש ב-FillInTheBlank ו-PopUpMenu, בקרת Drag & Drop ב-wantsDroppedMorph:event: ו-repelsMorph:event:, ופיתוח הקובייה DieMorph המשתמשת ב-perform:.

6. יכולות Canvas, תמונות חיצוניות ואנימציית Thread

ציור וטרנספורמציות ב-Canvas, טעינת תמונות חיצוניות עם ImageReadWriter formFromFileNamed: והקטנתן עם magnifyBy: 0.5, ואנימציה באמצעות step או תהליך נפרד [ ... ] fork.

עזר גרפי 17.10 • מפת העל של הסביבה הגרפית Morphic

כל המנגנונים של פרק 17 מחוברים סביב המחלקה Morph

Chapter 17 Synthesis
העצם החי Morph Directness & Liveness גיאומטריה והכלה (17.2–17.3) position:, extent:, addMorph: ציור וזיהוי פגיעה (17.4, 17.9) drawOn: aCanvas, containsPoint: אירועים ואנימציה (17.5, 17.10) handlesMouse..., step, [..] fork דו-שיח ותפריטים (17.6) FillInTheBlank, PopUpMenu גרירה והשמטה (17.7) wantsDroppedMorph:, repelsMorph: קוביית המשחק DieMorph (17.8) self perform: #faceX, self changed
⚡

מיני-סימולציה 17.10: בוחן התאמה מהיר לקראת המבחן – באיזו מתודה נשתמש?

לחץ על כל משימה כדי לחשוף את הפתרון המדויק
🃏
סימולציה אינטראקטיבית גדולה 17.10

כרטיסיות זיכרון תלת-ממדיות (3D Flashcards) לכל מושגי פרק 17

לחץ על כל כרטיסייה כדי להפוך אותה ולבחון את עצמך על כל אחד מ-12 מושגי המפתח של פרק 17:

נחשפו: 0 / 12
🚀

סיימנו במלואו את פרק 17 (הסביבה הגרפית Morphic)! מה מחכה לנו בחלק VII?

ראינו בפרק 17 איך העיקרון "הכל הוא עצם וכל חישוב מתרחש על ידי שליחת הודעה" קם לתחייה מול עינינו על המסך הגרפי של Squeak.

🎯 התחנה הבאה שלנו – חלק VII: מטה-מחלקות, פרק 18: המושג "מטה-מחלקה": בפרק 18 נגיע אל אחד משיאי העומק של תכנות מונחה עצמים: 10 כללי היסוד של מודל העצמים והמטה-מחלקות, מערכות עצמים ב-3, 4 ו-5 רמות, ומעגל הקסמים שבין Metaclass ל-Metaclass class!
🕹️

מעבדת חקר אינטראקטיבית

❓

5 שאלות פתוחות מדורגות + 5 שאלות אמריקאיות עם הסברים מלאים לכל יחידה

🚀 התחנה הבאה שלנו: חלק VII – פרק 18: המושג "מטה-מחלקה"

השלמנו במלואו את פרק 17: הסביבה הגרפית Morphic! התחנה הבאה שלנו היא חלק VII: מטה-מחלקות – פרק 18: המושג "מטה-מחלקה", שבו נחקור יחד: