Lulucat

To viskelær, og hvorfor viskingen tilhører strøket

Gaoge ZhangGaoge Zhang

Et viskestrøk som eget element er en opplagt løsning, men objektflytting bryter den. I Lulucat Notes tilhører viskingen derfor strøket som blekket fjernes fra.

Oppdatert

Når folk visker i en notatbok, kan de mene to forskjellige ting. Noen ganger er en markering feil, og hele markeringen skal bort: en hjelpelinje, et overstrøket ord eller et tilfeldig strøk fra en håndflate som hviler. Andre ganger er bare en del av markeringen feil: halve et tegn, én gren av et diagram eller halen på et highlighterstrøk som gikk forbi setningen. Lulucat Notes leverer nå begge deler som to separate verktøy.

Det vanskeligere designspørsmålet er hvor viskingen hører hjemme.

Strøkviskelæret kom først

Det første viskelæret slettet hele strøk. Ved hver berøring testes alle strøk: omsluttende bokser avviser nesten alle, og de som blir igjen, gjennomgår en test av kvadrert punkt-til-segment-avstand mot viskelærradiusen pluss strøkradiusen interpolert langs segmentet. Bredden varierer med trykket, så én nominell bredde ville slettet tynne strøk brukeren ikke traff og oversett tykke strøk som brukeren traff.

Én gest samles i én angreoppføring, og oppføringen lagrer hvert fjernet strøk sammen med den opprinnelige indeksen. Angre setter dem inn igjen i motsatt rekkefølge, slik at lagrekkefølgen kommer tilbake nøyaktig som før.

Dette viskelæret er raskt og forutsigbart, men kan ikke fjerne halve ord.

MaLiang gjør viskelæret til en pensel

MaLiang, det MIT-lisensierte tegningsrammeverket vi allerede har lånt highlighterens stempelmodell fra, løser delvis visking på 24 linjer. Dets Eraser er en underklasse av Brush som overstyrer én ting, blandingsalternativene, slik at den trekker fra i stedet for å legge til. Et viskestrøk er et element i samme liste som alle andre strøk. Angre fjerner dette elementet. Ingen eksisterende strøk endres, fordi viskingen bare skjer under avspilling, i listerekkefølge.

Vi leverte modellen, og to økter på enheter fikk den til å bryte sammen.

Objektflytting avdekker den manglende eieren

Den første rapporten var: velg strøk med lassoen, dra dem, og hullet blir liggende igjen.

Fire paneler som sammenligner to viskingsmodeller før og etter at strøkene flyttes. Når viskelæret er sitt eget element, blir gapet liggende på den gamle plasseringen; når viskingen eies av strøket, følger gapet blekket.

De samme strøkene med visking, flyttet mot høyre. Øverst: viskelæret er sitt eget element. Nederst: viskingen tilhører strøket.

Utvalgte strøk løftes ut av bitmapet til et overlegg som tegner dem direkte, uten viskelelementet. I det øyeblikket du valgte et strøk med visking, så det derfor helt ut igjen.

Den andre rapporten: viskelæret fløt over highlighteren. Rendereren vår spiller av i to passeringer, highlightere først og penner deretter, fordi highlighterblekk må ligge under blekk. Et viskelelement må delta i begge passeringene, ellers kan det ikke viske både highlightere og penner. Å delta i pennens passering betyr at det kjøres etter hver highlighter som er tegnet, så viskelæret skar også bort highlighterstrøk som ble tegnet senere i tid.

Begge feilene har samme opphav. MaLiang har verken lasso, flytting eller lagrekkefølge mellom pensler, så et element uten eier er tilstrekkelig der. Lerretet vårt har alle tre, og visking uten eier gir ikke noe entydig svar på «hva skjer når blekket flyttes» eller «hvilket lag opererer dette på».

Visking som eies av strøket

Alle Stroke-objekter bærer nå sine egne viskestier. Viskelæret tester hvilke strøk det berører og legger en sti til hvert av dem, i lerretskoordinater. Når ett strøk gjengis, tegnes blekket og dets egne viskinger i et transparenslag:

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 beholder destinasjonen og fjerner den proporsjonalt med kildealfaen:

Inne i et transparenslag er destinasjonen dette ene strøket, så operasjonen kan ikke nå papiret, strøkene under eller strøkene som tegnes senere. Laget koster ingenting for strøk uten visking, som gjelder nesten alle: de bruker den opprinnelige banen.

Eierforholdet løser begge feilene direkte. Translasjon flytter viskestiene sammen med målepunktene, så et flyttet strøk bærer med seg åpningene. Utvalgsoverlegget tegner det samme Stroke-objektet, så et valgt strøk viser de samme åpningene. Lagrekkefølgen slutter å bety noe, fordi en visking aldri gjelder noe annet enn sitt eget strøk.

Angre fungerer på gesten: én oppføring lister alle (stroke, path)-par som gesten produserte, angre fjerner stiene, og gjør om legger dem til igjen. Blekket under en visking blir aldri redigert, så ingenting må rekonstrueres.

Direkte tilbakemelding går gjennom tile-bitmapet

Strøket som tegnes, gjengis normalt som et overlegg over den sammensatte siden. Visking kan ikke fungere der, fordi dette overlegget ligger oppå både papir og blekk, og destination-out ville slå hull gjennom papiret.

Derfor skriver viskelæret direkte inn i strøkene, og hver bolk med nye målepunkter utløser en lokal avspilling av bare det berørte rektangelet. Tile-pipelinen tømmer allerede et rektangel og spiller av strøkene som krysser det; viskelæret gjenbruker denne banen og tilfører ingen ny tegningssemantikk.

Begge viskelærene, side om side

Verktøylinjen har to viskelærknapper i stedet for én knapp med en skjult modus. Det delvise viskelæret fjerner en del av et strøk. Strøkviskelæret fjerner hele strøk ved kontakt. Radiusene deres er separate konstanter, begge foreløpig 4 punkter.

Å slette et helt strøk tar med seg viskestiene, siden de er en del av strøket, og angre henter begge tilbake sammen. Viskestier er også del av den lagrede filen, kvantisert på samme måte som alt annet: radius og koordinater i tideler av et punkt.

En begrensning gjenstår. Et strøk som er visket ned til ingenting, eksisterer fortsatt som data. Det gjengis som ingenting, men lassoen kan fortsatt velge det, og strøkviskelæret kan fortsatt slette det, og det tar fortsatt plass i dokumentet. Å samle inn fullstendig viskede strøk krever en dekningskontroll vi ikke har skrevet, så foreløpig blir de værende.

Når en modell fra et annet prosjekt slutter å fungere i ditt, ligger forskjellen vanligvis ikke i koden du kopierte. Den ligger i funksjonene prosjektet ikke har.