Files
kniip/.cursor/plans/tekstballon_en_-wolk_fceb7079.plan.md
T
2026-11-10 15:33:49 +01:00

7.8 KiB

name, overview, todos, isProject
name overview todos isProject
Tekstballon en -wolk Het gereedschap "Tekst in kader" krijgt een vormkeuze (kader, ballon, ovale ballon, wolk). Ballon en wolk hebben een staart met een sleepbare greep op de punt, zodat je ermee iets kunt aanwijzen.
id content status
model TextShape enum, StyleSet.TextShape en StyleProps.TextShape toevoegen in StyleSet.cs completed
id content status
geometry TextAnnotation: TailTip, BuildShape (kader/ballon/ovaal/wolk), Bounds/HitTest/Move/handles completed
id content status
resize TextAnnotation: BoxWidth/BoxHeight, Box/TextArea, 8 formaatgrepen, woordafbreking, dubbelklik = automatisch; TextBox-editor op TextArea met vaste breedte completed
id content status
ui Vorm-keuzelijst + template in EditorWindow.xaml, wiring in UpdatePropsPanel en TextShape_Changed completed
id content status
create Standaard staartpunt bij nieuwe tekst; omtrek+staart zichtbaar tijdens bewerken completed
id content status
verify Bouwen en handmatig testen: vormen wisselen, staart slepen, verplaatsen, kopiëren/plakken, export completed
false

Tekstballon en -wolk voor "Tekst in kader"

Gedrag

  • In de eigenschappenbalk van het tekstgereedschap (en van een geselecteerd tekstobject) komt een nieuwe keuzelijst Vorm:
    • Kader: zoals nu, zonder staart.
    • Ballon: een rechthoek met een spitse staart. De bestaande keuze voor afgeronde hoeken blijft hierbij werken.
    • Ovale ballon: een ovaal om de tekst, met een spitse staart.
    • Wolk: een bolle wolkrand met als staart drie steeds kleinere rondjes (een gedachtewolk).
  • De punt van de staart is een greep. Je kunt die met het selectiegereedschap of het tekstgereedschap verslepen naar wat je wilt aanwijzen.
  • Als je het hele object verplaatst, schuift de staartpunt mee. Als de tekst langer wordt, blijft de punt op dezelfde plek staan, dus hij blijft naar hetzelfde wijzen.
  • Een nieuw object krijgt de staartpunt standaard schuin linksonder het kader.
  • Ligt de punt binnen de vorm, dan wordt er geen staart getekend.
  • De rand, vulling, stippellijn en schaduw lopen als één doorlopende omtrek om de vorm en de staart heen.
  • Formaat aanpassen (geldt voor alle vormen, ook Kader): een geselecteerd tekstobject krijgt 8 formaatgrepen rond de vorm, net als bij een rechthoek. Zodra je die versleept, krijgt het kader een vaste breedte en loopt de tekst daarbinnen door naar de volgende regel.
    • De hoogte die je instelt is een minimum. Past de tekst er niet in, dan groeit het kader naar beneden mee.
    • Is het kader hoger dan de tekst, dan staat de tekst verticaal in het midden.
    • Een nieuw tekstobject begint zoals nu, met een automatische grootte. Dubbelklik op een formaatgreep zet het kader terug naar automatisch.
    • De staartpunt blijft op zijn plek als je het formaat aanpast.

Technische aanpak

1. Datamodel in src/Kniip/Editor/StyleSet.cs

  • Nieuwe enum TextShape { Frame, Balloon, Oval, Cloud }.
  • Nieuwe stijleigenschap TextShape TextShape (standaard Frame). Oude instellingen blijven daardoor werken.
  • Nieuwe vlag StyleProps.TextShape, toe te voegen aan PropsFor(ToolKind.Text).
  • De gekozen vorm wordt per gereedschap onthouden via de bestaande ToolStyles.

2. Vorm tekenen in src/Kniip/Editor/Annotations/TextAnnotation.cs

  • Nieuwe eigenschap Point? TailTip. Zolang die leeg is, wordt de standaardpositie onder het kader berekend.
  • Nieuwe hulpfunctie BuildShape(Rect box) die de Geometry opbouwt:
    • Kader: de huidige RectangleGeometry.
    • Ballon (zoals in het voorbeeld met het rode kader dat naar het Google-logo wijst): een afgeronde rechthoek, samengevoegd met een spitse driehoekige staart via Geometry.Combine(..., GeometryCombineMode.Union). De voet van de staart zit op de zijde die naar de punt kijkt (links, rechts, boven of onder, afhankelijk van waar de punt ligt). Hij staat ongeveer midden op die zijde en schuift mee richting de punt, maar blijft buiten de afgeronde hoeken. De voet is ongeveer 25% van die zijde breed en de staart loopt spits uit naar de greep.
    • Ovaal: een EllipseGeometry die zo veel ruimer is dan het tekstvlak (factor ongeveer √2) dat de tekst erin past, plus dezelfde staart.
    • Wolk: een vereniging van cirkels langs de omtrek van een ovaal, plus het ovaal zelf. De staart bestaat uit drie losse rondjes van de rand naar de punt, die steeds kleiner worden.
  • Nieuwe eigenschappen double? BoxWidth en double? BoxHeight. Is null een automatische grootte, zoals nu.
  • Nieuwe hulpfuncties Box (de rechthoek van de vorm zonder staart, met Position linksboven) en TextArea (het tekstvlak daarbinnen):
    • Bij Kader en Ballon is TextArea gelijk aan Box min de Padding.
    • Bij Ovaal en Wolk springt TextArea extra in, zodat de tekst binnen de ronding blijft.
    • Bij een vaste breedte krijgt FormattedText.MaxTextWidth de breedte van TextArea, zodat de tekst op woorden afbreekt. De hoogte wordt max(BoxHeight, tekst + padding).
    • Bij een automatische grootte wordt Box uit de tekstmaat berekend, opgeblazen naar de vorm.
  • Render gebruikt BuildShape(Box) in plaats van de vaste rechthoek, en tekent de tekst verticaal gecentreerd in TextArea.
  • Bounds en HitTest nemen de buitengrenzen van de geometrie mee, inclusief de staart.
  • GetHandles() geeft eerst de 8 formaatgrepen van Box (zelfde volgorde als in RectAnnotation.cs) en daarna, behalve bij Kader, de staartpunt (index 8).
    • BeginHandleDrag onthoudt de oorspronkelijke Box.
    • MoveHandle voor index 0 tot en met 7 rekent de nieuwe rechthoek uit zoals RectAnnotation.MoveHandle, via de bestaande RectAnnotation.FromAnchor. Daarna zet het Position, BoxWidth en BoxHeight, met een minimale breedte van ongeveer twee tekens plus padding.
    • Index 8 past TailTip aan.
    • RemoveHandle (dubbelklik) op een formaatgreep zet BoxWidth en BoxHeight terug op null.
  • Move verschuift ook TailTip.

3. Editor in src/Kniip/Editor/EditorWindow.xaml en src/Kniip/Editor/EditorWindow.xaml.cs

  • Een TextShapeGroup met TextShapeCombo en een TextShapeTemplate (klein vormicoontje met een label), naar het voorbeeld van CursorKindTemplate en CursorKindCombo.
  • TextShapeValues als ItemsSource. In UpdatePropsPanel worden zichtbaarheid en selectie gezet. Een handler TextShape_Changed roept ChangeStyle("textshape", s => s.TextShape = shape) aan.
  • Bij het aanmaken van een nieuwe tekst (rond regel 842) wordt de standaard TailTip vastgezet. Het verslepen van de greep loopt via het bestaande generieke handle-mechanisme (rond regel 778), daar hoeft niets aan te veranderen.
  • BeginTextEdit en ApplyTextEditorStyle zetten de TextBox op TextArea in plaats van op Position. Bij een vaste breedte krijgt de TextBox die breedte en TextWrapping.Wrap, zodat je tijdens het typen dezelfde regelafbreking ziet als in het eindresultaat.
  • Tijdens het typen blijft de TextBox rechthoekig. De staart blijft dan zichtbaar, omdat alleen de tekstlaag verborgen is. Dat is een kleine aanpassing: bij een vorm met staart tekent Render de omtrek ook als IsHidden aan staat, alleen de tekst niet.
flowchart LR
    TextShapeCombo --> ChangeStyle --> StyleSetTextShape["StyleSet.TextShape"]
    StyleSetTextShape --> BuildShape
    TailTip --> BuildShape
    BuildShape --> Render
    BuildShape --> Bounds
    HandleDrag["Greep slepen"] --> TailTip

Buiten scope

  • Een explosie- of uitroepvorm en gebogen staarten. Die zijn later toe te voegen als extra TextShape-waarde.
  • Draaien van het kader (de draaigreep boven het kader in het voorbeeld).