Lulucat

ممحاتان، ولماذا يخصّ المحو الضربة

Gaoge ZhangGaoge Zhang

من الطبيعي تصميم ضربة الممحاة كعنصر مستقل. لكن نقل الكائنات يكسره، لذلك يخصّ المحو في Lulucat Notes الضربة التي يزيل حبرها.

حُدّث

يقصد الناس شيئين مختلفين عندما يمحون في دفتر ملاحظات. أحيانًا تكون علامة ما خاطئة ويجب أن تختفي العلامة كلها: خط إرشادي، أو كلمة مشطوبة، أو ضربة شاردة من راحة يد مستندة. وأحيانًا يكون الجزء الخاطئ من العلامة فقط: نصف حرف، أو فرع من مخطط، أو ذيل ضربة قلم تمييز تجاوزت الجملة. يقدّم Lulucat Notes الآن كليهما، على هيئة أداتين منفصلتين.

السؤال التصميمي الأصعب هو أين يعيش المحو.

بدأت ممحاة الضربات أولًا

كانت الممحاة الأولى تحذف الضربات كاملة. يفحص اللمس كل ضربة: تستبعد الصناديق المحيطة جميع الضربات تقريبًا، ثم يخضع ما ينجو منها لاختبار مربع المسافة من النقطة إلى المقطع، مقابل نصف قطر الممحاة مضافًا إليه نصف قطر الضربة المحسوب بالاستيفاء على طول ذلك المقطع. يتغير العرض مع الضغط، لذلك كان عرض اسمي واحد سيحذف الضربات الرفيعة التي لم يلمسها المستخدم، ويفوّت الضربات السميكة التي لمسها.

تُدمج الإيماءة الواحدة في إدخال تراجع واحد، ويخزن الإدخال كل ضربة محذوفة مع فهرسها الأصلي. يعيد التراجع إدراجها بترتيب عكسي، فتعود طبقات التراص كما كانت تمامًا.

هذه الممحاة سريعة ومتوقعة، لكنها لا تستطيع حذف نصف كلمة.

يحوّل MaLiang الممحاة إلى فرشاة

MaLiang، إطار الرسم المرخّص بموجب MIT الذي استعرنا منه بالفعل نموذج طوابع قلم التمييز، يجيب عن المحو الجزئي في أربعة وعشرين سطرًا. إن Eraser فيه فئة فرعية من Brush تتجاوز شيئًا واحدًا، هو خيارات المزج، لكي تطرح بدلًا من أن تضيف. ضربة الممحاة عنصر في القائمة نفسها التي تضم كل ضربة أخرى. يزيل التراجع ذلك العنصر. ولا تُعدّل أي ضربة موجودة، لأن المحو يحدث فقط أثناء إعادة التشغيل، بترتيب القائمة.

أطلقنا ذلك النموذج، فأفسدته جلستا استخدام على جهازين.

يكشف نقل الكائنات عن المالك المفقود

جاء البلاغ الأول: حدّد الضربات بأداة اللاسو، واسحبها، فتبقى الفجوة خلفها.

أربع لوحات تقارن نموذجي ممحاة قبل نقل الضربات وبعده. عندما تكون الممحاة عنصرًا مستقلًا تبقى الفجوة في موضعها القديم؛ وعندما يخصّ المحو الضربة تنتقل الفجوة مع الحبر.

الضربات نفسها بعد محوها ونقلها إلى اليمين. في الأعلى: الممحاة عنصر مستقل. في الأسفل: يخصّ المحو الضربات.

جعل التحديد الأمر أسوأ. تُرفع الضربات المحددة من الصورة النقطية إلى طبقة تراكب ترسمها مباشرةً، من دون عنصر الممحاة، ولذلك ظهرت الضربة الممحاة كاملةً من جديد لحظة تحديدها.

وجاء البلاغ الثاني: كانت الممحاة تطفو فوق قلم التمييز. يعيد المصيّر الرسم على مرحلتين، أقلام التمييز أولًا والأقلام ثانيًا، لأن حبر قلم التمييز يجب أن يكون تحت الحبر. يجب أن يشارك عنصر الممحاة في المرحلتين، وإلا فلن يستطيع محو النوعين معًا. وتعني مشاركته في مرحلة الأقلام تشغيله بعد رسم كل قلم تمييز، ولذلك كانت الممحاة تقطع أيضًا ضربات قلم التمييز التي رُسمت في وقت لاحق.

للفشلين مصدر واحد. لا يملك MaLiang أداة لاسو ولا نقلًا ولا ترتيب طبقات بين الفرش، ولذلك يكفيه عنصر بلا مالك. أما لوحة الرسم لدينا فتملك الميزات الثلاث، والمحو بلا مالك لا يملك إجابة محددة عن «ماذا يحدث عندما يتحرك الحبر؟» أو «على أي طبقة تعمل هذه العملية؟».

المحو تابع للضربة

تحمل كل Stroke الآن مسارات المحو الخاصة بها. تختبر الممحاة الضربات التي تلامسها، وتضيف مسارًا إلى كل واحدة منها، بإحداثيات اللوحة. وعند تصيير ضربة واحدة، يُرسم حبرها ومحوها الخاص داخل طبقة شفافية:

context.beginTransparencyLayer(auxiliaryInfo: nil)
drawInk(stroke, in: context, clip: clip, colorOverride: colorOverride)
context.setBlendMode(.destinationOut)
for path in stroke.erasePaths { fillErasePath(path, in: context) }
context.endTransparencyLayer()

يحافظ Destination-out على الوجهة ويزيلها بنسبة ألفا المصدر:

داخل طبقة شفافية تكون الوجهة هي تلك الضربة وحدها، ولذلك لا يمكن للعملية أن تصل إلى الورق، أو إلى الضربات الموجودة تحتها، أو إلى الضربات التي تُرسم لاحقًا. ولا تكلّف الطبقة شيئًا للضربات التي بلا محو، وهي الغالبية تقريبًا؛ فتلك تستخدم المسار الأصلي.

تجيب الملكية عن الفشلين مباشرةً. يحرّك النقل مسارات المحو مع نقاط العينة، فتحمل الضربة المنقولة فجواتها معها. وترسم طبقة التحديد كائن الضربة نفسه، ولذلك تعرض الضربة المحددة الفجوات نفسها. ولم يعد ترتيب الطبقات مهمًا، لأن المحو لا ينطبق على شيء سوى ضربته الخاصة.

يعمل التراجع على مستوى الإيماءة: يسرد إدخال واحد كل زوج (stroke, path) أنتجته تلك الإيماءة، فيزيل التراجع تلك المسارات، وتضيفها الإعادة مرة أخرى. ولا يُعدّل الحبر الموجود تحت المحو قط، لذلك لا شيء يحتاج إلى إعادة بناء.

تمرّ المعاينة الفورية عبر الصورة النقطية للبلاطات

تُصيّر الضربة الجاري رسمها عادةً كتراكب فوق الصفحة المركّبة. ولا يمكن للمحو أن يعمل هناك، لأن طبقة التراكب هذه تقع فوق الورق والحبر معًا، ولأن destination-out سيخترق الورق أيضًا.

لذلك تكتب الممحاة في الضربات فورًا، وتطلق كل دفعة من العينات الجديدة إعادة تشغيل محلية للمستطيل المتأثر وحده. فخط أنابيب البلاطات يمسح مستطيلًا بالفعل ويعيد تشغيل الضربات التي تتقاطع معه؛ وتعيد الممحاة استخدام هذا المسار ولا تضيف دلالة رسم ثانية.

الممحاتان جنبًا إلى جنب

يضم شريط الأدوات زري ممحاة بدلًا من زر واحد ذي نمط مخفي. تزيل الممحاة الجزئية جزءًا من الضربة. وتحذف ممحاة الضربات الضربات كاملةً عند التلامس. ونصفا قطريهما ثابتان منفصلان، وكلاهما حاليًا 4 نقاط.

يحذف حذف ضربة كاملة مسارات محوها معها، لأنها جزء من تلك الضربة، ويعيد التراجع الاثنين معًا. وتُعد مسارات المحو جزءًا من الملف المحفوظ أيضًا، وتُكمَّم مثل كل شيء آخر: نصف القطر والإحداثيات إلى أعشار النقطة.

يبقى قيد واحد. فالضربة التي مُحيت حتى اختفت تمامًا تظل موجودة كبيانات. وهي لا تُصيّر شيئًا، لكن أداة اللاسو تظل قادرة على تحديدها، وتظل ممحاة الضربات قادرة على حذفها، كما تظل تشغل مساحة في المستند. ويتطلب جمع الضربات الممحاة بالكامل اختبار تغطية لم نكتبه بعد، ولذلك تبقى هذه الضربات في الوقت الحالي.

عندما يتوقف نموذج من مشروع آخر عن العمل في مشروعك، لا يكمن الاختلاف عادةً في الكود الذي نسخته. بل يكمن في الميزات التي لا يملكها ذلك المشروع.