Lulucat

İki Silgi ve Silmenin Neden Çizgiye Ait Olduğu

Gaoge ZhangGaoge Zhang

Silgi çizgisini ayrı bir öğe yapmak bariz bir tasarımdır. Nesne taşımak bunu bozar; Lulucat Notes'ta silme, mürekkebini sildiği çizgiye aittir.

Güncellendi

İnsanlar bir defterde bir şey sildiklerinde iki farklı şeyi kasteder. Bazen bir işaret yanlıştır ve işaretin tamamı gitmelidir: bir kılavuz çizgisi, üstü çizilmiş bir kelime veya kâğıda dayanan avuçtan kalan başıboş bir çizgi. Bazen işaretin yalnızca bir bölümü yanlıştır: bir karakterin yarısı, bir diyagramın dalı veya cümlenin ötesine taşmış bir fosforlu kalem süpürmesinin kuyruğu. Lulucat Notes artık ikisini iki ayrı araç olarak sunuyor.

Daha zor tasarım sorusu, silmenin nerede tutulacağıdır.

Çizgi silgisi önce geldi

İlk silgi bütün çizgileri siliyordu. Bir dokunuş her çizgiyi test eder: sınırlayıcı kutular neredeyse hepsini eler, kalanlar da silgi yarıçapı ile bu doğru parçası boyunca enterpole edilen çizgi yarıçapının toplamına göre point-to-segment squared distance testiyle sınanır. Genişlik basınca göre değiştiği için tek bir nominal genişlik, kullanıcının dokunmadığı ince çizgileri siler ve dokunulan kalın çizgileri kaçırır.

Bir hareket tek bir geri alma girdisinde birleşir ve bu girdi, kaldırılan her çizgiyi özgün diziniyle birlikte saklar. Geri alma bunları ters sırayla yeniden ekler; böylece yığma sırası tam olarak eski hâline döner.

Bu silgi hızlı ve öngörülebilirdir; bir kelimenin yarısını silemez.

MaLiang silgiyi fırçaya dönüştürüyor

MaLiang, highlighter stamp modelini daha önce ödünç aldığımız MIT lisanslı çizim çatısı, kısmi silmeyi 24 satırda çözer. Eraser, toplamak yerine çıkarmak için blend seçeneklerini geçersiz kılan bir Brush alt sınıfıdır. Bir silgi çizgisi, diğer tüm çizgilerle aynı listedeki bir öğedir. Geri alma bu öğeyi kaldırır. Mevcut hiçbir çizgi değiştirilmez, çünkü silme yalnızca yeniden oynatma sırasında, liste sırasına göre gerçekleşir.

Bu modeli yayına aldık; iki cihaz oturumu onu bozdu.

Nesneyi taşımak, bir sahibin olmadığını ortaya çıkarıyor

İlk bildirim: çizgileri kementle seçin, sürükleyin ve boşluk geride kalıyor.

İki silgi modelini çizgileri taşımadan önce ve sonra karşılaştıran dört panel. Silginin kendi öğesi olduğu durumda boşluk eski konumunda kalıyor; silme çizgiye ait olduğunda boşluk mürekkeple birlikte taşınıyor.

Aynı silinmiş çizgiler sağa taşındı. Üstte: silgi kendine ait bir öğe. Altta: silme çizgilere ait.

Seçim durumu bunu daha da kötüleştirdi. Seçilen çizgiler bitmap’ten, silgi öğesi olmadan doğrudan çizildikleri bir kaplamaya kaldırılır; bu yüzden silinmiş bir çizgiyi seçtiğiniz anda yeniden bütün görünür.

İkinci bildirim: silgi fosforlu kalemin üzerinde yüzüyordu. Renderlayıcımız iki geçişte yeniden oynatır: fosforlu kalemler önce, kalemler sonra; çünkü fosforlu kalem mürekkebi mürekkebin altında durmalıdır. Bir silgi öğesi her iki geçişe de katılmalıdır; aksi hâlde iki türü de silemez. Kalem geçişine katılması, her fosforlu kalem çizildikten sonra çalıştırılması anlamına gelir; bu da silginin zaman içinde daha sonra çizilmiş fosforlu kalem çizgilerini de kesmesine yol açar.

İki başarısızlığın kaynağı aynı. MaLiang’da kement, taşıma ve fırçalar arasında katman sırası yok; bu yüzden sahibi olmayan bir öğe orada yeterlidir. Bizim tuvalimizde bu üçünün de bulunması, sahibi olmayan bir silmenin mürekkep taşındığında ne olacağı veya bu işlemin hangi katmanda çalıştığı sorularına tanımlı bir yanıt vermediği anlamına gelir.

Silme çizgiye ait

Artık her Stroke kendi silme yollarını taşıyor. Silgi hangi çizgilere dokunduğunu test eder ve her birine tuval koordinatlarında bir yol ekler. Tek bir çizgi çizilirken, mürekkebi ve kendi silmeleri bir saydamlık katmanında çizilir:

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 hedefi korur ve kaynak alfa oranında azaltır:

Bir saydamlık katmanının içinde hedef o tek çizgidir; bu nedenle işlem kâğıda, alttaki çizgilere veya daha sonra çizilen çizgilere ulaşamaz. Katman, silmesi olmayan çizgiler için hiçbir maliyet oluşturmaz; bunlar neredeyse hepsidir: o çizgiler özgün yolu kullanır.

Sahiplik iki başarısızlığı doğrudan çözer. Öteleme, silme yollarını örnek noktalarıyla birlikte taşır; böylece taşınan bir çizgi boşluklarını beraberinde götürür. Seçim kaplaması aynı çizgi nesnesini çizer; bu yüzden seçili bir çizgi aynı boşlukları gösterir. Katman sırası artık önemli değildir, çünkü bir silme kendi çizgisi dışındaki hiçbir şeye uygulanmaz.

Geri alma hareket temelinde çalışır: bir girdi, o hareketin ürettiği tüm (stroke, path) çiftlerini listeler; geri alma bu yolları kaldırır, yineleme onları tekrar ekler. Silmenin altındaki mürekkep hiçbir zaman düzenlenmez; dolayısıyla hiçbir şeyin yeniden oluşturulması gerekmez.

Canlı geri bildirim döşeme bitmap’inden geçer

Çizilmekte olan çizgi normalde bileşik sayfanın üstünde bir kaplama olarak render edilir. Silme orada çalışamaz; çünkü bu kaplama kâğıdın yanı sıra mürekkebin de üstünde durur ve destination-out kâğıtta da delik açar.

Bu nedenle silgi hemen çizgilerin içine yazar ve her yeni örnek grubunda yalnızca etkilenen dikdörtgenin yerel yeniden oynatmasını tetikler. Döşeme işlem hattı zaten bir dikdörtgeni temizleyip onunla kesişen çizgileri yeniden oynatıyor; silgi bu yolu yeniden kullanır ve ikinci bir çizim anlamı eklemez.

İki silgi, yan yana

Araç çubuğunda gizli modlu tek düğme yerine iki silgi düğmesi bulunur. Kısmi silgi bir çizginin bir bölümünü çıkarır. Çizgi silgisi dokunduğu anda bütün çizgileri kaldırır. Yarıçapları ayrı sabitlerdir; ikisi de şu anda 4 punto.

Bütün bir çizgiyi silmek, silme yollarını da çizginin parçası oldukları için beraberinde kaldırır; geri alma da ikisini birlikte geri getirir. Silme yolları kaydedilmiş dosyanın da parçasıdır ve diğer her şey gibi niceleme uygulanır: yarıçap ve koordinatlar bir point’in onda biri cinsindendir.

Bir sınırlama kaldı. Hiçliğe kadar silinen bir çizgi veri olarak hâlâ vardır. Hiçbir şey olarak render edilmez, ancak kement onu seçebilir ve çizgi silgisi onu hâlâ silebilir; belgede hâlâ yer kaplar. Tamamen silinmiş çizgileri toplamak, henüz yazmadığımız bir kapsama testi gerektiriyor; şimdilik bu yüzden kalıyorlar.

Başka bir projedeki bir model sizinkinde çalışmayı bıraktığında fark genellikle kopyaladığınız kodda değildir. Fark, o projenin sahip olmadığı özelliklerdedir.