Dvije gumice i zašto brisanje pripada potezu
Gumica kao vlastiti element očit je dizajn. Premještanje objekta to razbija, pa u Lulucat Notesu brisanje pripada potezu iz kojeg uklanja tintu.
Kad ljudi brišu u bilježnici, time mogu misliti na dvije različite stvari. Ponekad je oznaka pogrešna i cijela mora nestati: pomoćna crta, prekrižena riječ, zalutali potez dlana oslonjenog na stranicu. Ponekad je pogrešan samo dio oznake: polovica znaka, jedna grana dijagrama, rep poteza markera koji je prešao preko rečenice. Lulucat Notes sada nudi oboje, kao dva zasebna alata.
Teže je pitanje dizajna gdje brisanje pripada.
Gumica za poteze stigla je prva
Prva je gumica brisala cijele poteze. Pri svakom dodiru testira svaki potez: okviri omeđenja odbacuju gotovo sve, a preživjeli prolaze test kvadrirane udaljenosti od točke do segmenta u odnosu na polumjer gumice uvećan za polumjer poteza interpoliran duž tog segmenta. Širina se mijenja s pritiskom, pa bi jedna nominalna širina izbrisala tanke poteze koje korisnik nije dotaknuo i propustila debele koje jest.
Jedna gesta stapa se u jedan unos za poništavanje, a taj unos pohranjuje svaki uklonjeni potez zajedno s njegovim izvornim indeksom. Poništavanje ih vraća obrnutim redoslijedom, pa se redoslijed slaganja vraća točno na prethodno stanje.
Ova je gumica brza, predvidljiva i ne može ukloniti polovicu riječi.
MaLiang od gumice radi kist
MaLiang, okvir za crtanje pod licencom MIT iz kojeg smo već preuzeli model pečata za marker, na djelomično brisanje odgovara u dvadeset i četiri retka. Njegov je Eraser podklasa klase Brush koja nadjačava samo jednu stvar, opcije miješanja, kako bi oduzimala umjesto dodavala. Potez gumice element je na istom popisu kao i svaki drugi potez. Poništavanje uklanja taj element. Nijedan postojeći potez ne mijenja se jer se brisanje događa samo pri ponovnom iscrtavanju, redoslijedom popisa.
Isporučili smo taj model, a dvije sesije na uređajima razbile su ga.
Premještanje objekata otkriva nedostajućeg vlasnika
Prva je prijava glasila: odaberite poteze lasom, povucite ih i praznina ostaje iza.

Isti izbrisani potezi premješteni udesno. Gore: gumica je vlastiti element. Dolje: brisanje pripada potezima.
Odabir je stvar pogoršao. Odabrani se potezi izdvajaju iz bitmape u prekrivni sloj koji ih izravno iscrtava, bez elementa gumice, pa se odabrani izbrisani potez u trenutku odabira ponovno pojavio cijeli.
Druga je prijava glasila: gumica je plutala iznad markera. Naš renderer ponovno iscrtava u dva prolaza, prvo markere, a zatim olovke, jer tinta markera mora biti ispod tinte. Element gumice mora sudjelovati u oba prolaza, inače ne može brisati obje vrste. Sudjelovanje u prolazu olovke znači da se izvršava nakon iscrtavanja svakog markera, pa je gumica izrezala i poteze markera koji su u vremenu nacrtani poslije.
Oba kvara imaju isti izvor. MaLiang nema laso, premještanje ni redoslijed slojeva između kistova, pa je element bez vlasnika tamo dovoljan. Naše platno ima sve troje, a brisanje bez vlasnika nema definiran odgovor na pitanje „što se događa kad se tinta premjesti” ni „na kojem sloju ovo djeluje”.
Brisanje pripada potezu
Svaki Stroke sada nosi vlastite putanje brisanja. Gumica provjerava koje poteze dodiruje i svakom dodaje putanju, u koordinatama platna. Iscrtavanje jednog poteza iscrtava njegovu tintu i vlastita brisanja unutar sloja prozirnosti:
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 zadržava odredište i uklanja ga razmjerno alfa-vrijednosti izvora:
Unutar sloja prozirnosti odredište je taj jedan potez, pa operacija ne može dosegnuti papir, poteze ispod njega ni poteze iscrtane kasnije. Sloj ne donosi trošak potezima bez brisanja, a takvi čine gotovo sve poteze: oni koriste izvornu putanju.
Vlasništvo izravno odgovara na oba kvara. Translacija pomiče putanje brisanja zajedno s uzorkovanim točkama, pa premješteni potez nosi svoje praznine. Prekrivni sloj odabira iscrtava isti objekt Stroke, pa odabrani potez pokazuje iste praznine. Redoslijed slojeva više nije bitan jer brisanje nikad ne djeluje ni na što osim na vlastiti potez.
Poništavanje radi na razini geste: jedan unos navodi svaki par (stroke, path) koji je ta gesta proizvela, poništavanje uklanja te putanje, a ponovno izvršavanje ih ponovno dodaje. Tinta ispod brisanja nikad se ne uređuje, pa ništa ne treba rekonstruirati.
Povratna informacija uživo prolazi kroz bitmapu pločica
Potez koji se crta obično se iscrtava kao prekrivni sloj iznad kompozitirane stranice. Brisanje tamo ne može raditi jer taj prekrivni sloj leži iznad papira jednako kao i iznad tinte, a destination-out bi probio i papir.
Zato gumica odmah zapisuje u poteze, a svaki paket novih uzoraka pokreće lokalno ponovno iscrtavanje samo zahvaćenog pravokutnika. Cjevovod pločica već briše pravokutnik i ponovno iscrtava poteze koji ga sijeku; gumica ponovno koristi taj put i ne dodaje drugu semantiku iscrtavanja.
Obje gumice, jedna uz drugu
Alatna traka ima dva gumba gumice umjesto jednog gumba sa skrivenim načinom rada. Djelomična gumica uklanja dio poteza. Gumica za poteze uklanja cijele poteze pri dodiru. Njihovi su polumjeri zasebne konstante, a oba trenutačno iznose četiri točke.
Brisanje cijelog poteza odnosi i njegove putanje brisanja jer su one dio tog poteza, a poništavanje vraća oboje zajedno. Putanje brisanja također su dio spremljene datoteke i kvantiziraju se kao i sve ostalo: polumjer i koordinate na desetinku točke.
Ostaje jedno ograničenje. Potez izbrisan do kraja i dalje postoji kao podatak. Na prikazu se ne vidi, ali laso ga i dalje može odabrati, gumica za poteze i dalje ga može izbrisati, a on i dalje zauzima prostor u dokumentu. Uklanjanje potpuno izbrisanih poteza zahtijeva test pokrivenosti koji još nismo napisali, pa zasad ostaju.
Kad model iz drugog projekta prestane raditi u vašem, razlika obično nije u kodu koji ste kopirali. Ona je u značajkama koje taj projekt nema.