This commit is contained in:
rogier committed 2026-11-10 15:33:49 +01:00
1 parent 36c55c034c
commit c2f8063518
34 files changed
+3039 -136

No files matched your search

@@ -0,0 +1,52 @@
---
name: Drie-split werkbalk
overview: "Een derde editorindeling toevoegen: gereedschappen boven, opslaan/kopiëren/zoom onder, en Nieuw + Screencast in een verticale zijbalk links of rechts van het canvas. De zijde is te kiezen in Instellingen > Weergave en via rechtsklik op de werkbalk."
todos:
- id: settings-model
content: EditorLayout.ThreeSplit en CaptureBarPosition toevoegen in AppSettings
status: pending
- id: editor-xaml
content: CaptureBar/CaptureItems toevoegen in EditorWindow.xaml
status: pending
- id: editor-logic
content: ApplyToolbarLayout, positie, lock, Ctrl+H, contextmenu en reset aanpassen in EditorWindow.xaml.cs
status: pending
- id: settings-ui
content: Derde optie plus Links/Rechts in SettingsWindow (xaml + laden/opslaan/standaard)
status: pending
- id: verify
content: Bouwen en alle indelingen handmatig testen
status: pending
isProject: false
---
# Drie-split werkbalk met zijbalk links of rechts
## Gedrag
- Nieuwe indeling "Drie-split": bovenbalk (gereedschappen), onderbalk (Opslaan, Uploaden, Kopiëren, zoom) en een smalle verticale zijbalk naast het canvas met **Nieuw** en **Screencast**.
- De zijbalk staat links of rechts (standaard links). In de zijbalk staan alleen iconen; de bestaande logica in `UpdateActionLabels` verbergt labels al bij `Orientation.Vertical`.
- De zijbalk staat in de `DockPanel` na `Toolbar`/`PropsBar`, zodat hij alleen naast het canvas loopt (zoals op positie 2 en 3 in je screenshot) en niet naast de boven- of onderbalk.
## Wijzigingen
**[src/Kniip/Settings/AppSettings.cs](src/Kniip/Settings/AppSettings.cs)**
- `EditorLayout` uitbreiden met `ThreeSplit` (achteraan, zodat bestaande instellingen geldig blijven).
- Nieuwe instelling `public Dock CaptureBarPosition { get; set; } = Dock.Left;` (alleen `Left`/`Right` gebruikt).
**[src/Kniip/Editor/EditorWindow.xaml](src/Kniip/Editor/EditorWindow.xaml)**
- Nieuwe `Border x:Name="CaptureBar"` (standaard `Collapsed`) met `local:ToolbarPanel x:Name="CaptureItems"` (verticaal), met dezelfde `ContextMenuOpening`/`OrderChanged`-handlers als `ActionBar`. Plaats hem na `PropsBar` en vóór het canvas.
**[src/Kniip/Editor/EditorWindow.xaml.cs](src/Kniip/Editor/EditorWindow.xaml.cs)**
- `IsSplitLayout` true maken voor zowel `Split` als `ThreeSplit` (beide gebruiken `SplitToolbarOrder` en `SplitBottomIds`), plus `IsThreeSplit`.
- `SideIds = { "New", "Screencast" }`; in `ApplyToolbarLayout` gaan die items bij `ThreeSplit` naar `CaptureItems` in plaats van `ActionItems`. `CaptureItems.Children` ook leegmaken.
- `CaptureBar.Visibility` instellen op dezelfde manier als `ActionBar` (ook in `ToggleToolbars`, Ctrl+H). `DockPanel.SetDock(CaptureBar, _settings.CaptureBarPosition)` en de rand aan de canvaszijde (`0,0,1,0` links, `1,0,0,0` rechts).
- `AllToolbarItems` uitbreiden met `CaptureItems.Items`; `ApplyToolbarLock` ook voor `CaptureItems`/`CaptureBar`.
- Contextmenu: in "Indeling" een derde optie "Drie-split: zijbalk met Nieuw en Screencast". Bij `ThreeSplit` een submenu "Zijbalk" met Links/Rechts (zelfde patroon als het bestaande "Positie"-menu). `Toolbar_ContextMenuOpening` het juiste paneel laten kiezen voor `CaptureBar`.
- `ResetToolbar`: `CaptureBarPosition = Dock.Left`.
**[src/Kniip/Settings/SettingsWindow.xaml](src/Kniip/Settings/SettingsWindow.xaml)** en **[.xaml.cs](src/Kniip/Settings/SettingsWindow.xaml.cs)**
- Derde radiobutton `LayoutThreeSplitRadio` "Drie-split: gereedschappen boven, opslaan onder, Nieuw en Screencast opzij", met daaronder (ingesprongen, alleen actief bij deze keuze) twee radiobuttons "Links" en "Rechts".
- Laden (r. 104-105) en opslaan (r. 454) aanpassen voor drie waarden plus `CaptureBarPosition`; standaardinstellingen (r. ~481) ook terugzetten.
## Controle
- Bouwen, editor openen, tussen alle drie de indelingen wisselen via Instellingen en rechtsklik. Controleren dat Links/Rechts direct werkt, Ctrl+H alle balken verbergt en terughaalt, en dat versleepte volgorde en verborgen knoppen bewaard blijven.
@@ -0,0 +1,90 @@
---
name: Meertaligheid kniip
overview: kniip meertalig maken (Nederlands, Engels, Duits, Frans, Spaans, Italiaans, Portugees) met een taalinstelling die standaard de Windows-taal volgt en direct wisselt zonder herstart, net als de kleurmodus. Daarnaast de installer en de README vertalen.
todos:
- id: infra
content: LanguageManager, AppLanguage-instelling, Strings.*.xaml-skelet en opstart-aanroep
status: pending
- id: settings-ui
content: Taalkeuze op tab Weergave in SettingsWindow met direct wisselen
status: pending
- id: xaml
content: Alle XAML-teksten omzetten naar DynamicResource (nl-bron)
status: pending
- id: csharp
content: C#-teksten omzetten naar LanguageManager.T, inclusief tray en verversen bij Changed
status: pending
- id: translate
content: Vertalingen en, de, fr, es, it, pt invullen en controlescript voor sleutels in tools/
status: pending
- id: installer
content: "Inno Setup: zeven talen, CustomMessages, eerste taal doorgeven aan de app"
status: pending
- id: docs
content: README in zeven talen, CHANGELOG-entry
status: pending
- id: verify
content: Bouwen, elke taal doorlopen, breedtes van lange teksten (de, fr, es, it, pt) nalopen
status: pending
isProject: false
---
# Meertaligheid kniip (nl, en, de, fr, es, it, pt)
## Aanpak
Zelfde patroon als [src/Kniip/Infrastructure/ThemeManager.cs](src/Kniip/Infrastructure/ThemeManager.cs): per taal een `ResourceDictionary` met strings, die in `Application.Current.Resources.MergedDictionaries` wordt gewisseld. XAML gebruikt `DynamicResource`, dus alle open vensters wisselen direct mee.
```mermaid
flowchart LR
Settings["AppSettings.Language"] --> LangMgr["LanguageManager.Apply"]
LangMgr --> Dict["Strings.xx.xaml in MergedDictionaries"]
Dict --> Xaml["XAML: DynamicResource"]
LangMgr --> Changed["LanguageManager.Changed"]
Changed --> CodeTexts["C#-teksten, tray, open menu's verversen"]
LangMgr --> Culture["CurrentUICulture en CurrentCulture"]
```
## Onderdelen
**1. Taalbestanden** in de nieuwe map `src/Kniip/Languages/`:
- `Strings.nl.xaml` (bron, huidige teksten), `Strings.en.xaml`, `Strings.de.xaml`, `Strings.fr.xaml`, `Strings.es.xaml`, `Strings.it.xaml`, `Strings.pt.xaml`
- Portugees is Braziliaans Portugees (de grootste groep gebruikers) en wordt ook gebruikt voor Windows in pt-PT
- Sleutels in de vorm `Settings.Hotkeys.Title`, `Editor.Menu.Undo`, `Tray.PauseCapture`; teksten met variabelen gebruiken `{0}` voor `string.Format`
- Een ontbrekende sleutel in een taal valt terug op Engels (Engels wordt onder de gekozen taal gemerged)
**2. `LanguageManager`** (nieuw, `src/Kniip/Infrastructure/LanguageManager.cs`):
- `Apply(AppLanguage)`: wisselt de dictionary, zet `CultureInfo` (datums, getallen) en het `Language`-attribuut van open vensters, en roept daarna `Changed` aan
- `T(string key)` en `T(string key, params object[] args)` voor C#-code
- Bij `System` de Windows-UI-taal (`CultureInfo.InstalledUICulture`) nemen; staat die niet in de lijst, dan Engels
**3. Instelling** in [src/Kniip/Settings/AppSettings.cs](src/Kniip/Settings/AppSettings.cs):
- `enum AppLanguage { System, Dutch, English, German, French, Spanish, Italian, Portuguese }`, met `public AppLanguage Language { get; set; } = AppLanguage.System;`
- In [src/Kniip/Settings/SettingsWindow.xaml](src/Kniip/Settings/SettingsWindow.xaml), tab Weergave, een kopje "Taal" boven Kleurmodus met een ComboBox. Taalnamen staan altijd in de eigen taal (Nederlands, English, Deutsch, Français, Español, Italiano, Português), en een wissel geldt direct, net als bij de kleurmodus
- Aanroep van `LanguageManager.Apply` bij het opstarten in [src/Kniip/App.xaml.cs](src/Kniip/App.xaml.cs), vóór `ThemeManager.Apply` en vóór het eerste venster
**4. XAML omzetten**: elke `Text`, `Content`, `ToolTip`, `Header` en `Title` wordt `{DynamicResource ...}`. Op volgorde van omvang:
- `SettingsWindow.xaml` (ongeveer 110 teksten), `EditorWindow.xaml` (ongeveer 100), `ScreencastWindow.xaml`, `HistoryWindow.xaml`, `RecordingToolbar.xaml`, `SnipToast.xaml`, `CountdownWindow.xaml`, `RegionSelectorWindow.xaml`, `RegionCaptureWindow.xaml`, `ColorPickerButton.xaml`, `EmojiPickerButton.xaml`, `App.xaml`
**5. C#-teksten omzetten** naar `LanguageManager.T(...)`:
- Menu's die bij openen worden opgebouwd (contextmenu's in `EditorWindow.xaml.cs`, werkbalkmenu) zijn daarmee meteen goed
- Teksten die langer blijven staan (bijvoorbeeld `HintText`, statusteksten, labels in `SettingsWindow.xaml.cs` en `ScreencastWindow.xaml.cs`) krijgen `SetResourceReference` of verversen bij `LanguageManager.Changed`
- Tray ([src/Kniip/Infrastructure/TrayIcon.cs](src/Kniip/Infrastructure/TrayIcon.cs), WinForms) bouwt de menu-teksten opnieuw op bij `Changed`
- Overige bestanden: `App.xaml.cs`, `HistoryActions.cs`, `HistoryWindow.xaml.cs`, `UploaderImporter.cs`, `ZiplineUploader.cs`, `RecordingController.cs`, `UpdateInstaller.cs`, `UpdateChecker.cs`, `ShellIntegration.cs`, `HotkeyGesture.cs` (namen zoals "Geen"), `EmojiCatalog.cs` (categorienamen), `MarkdownRenderer.cs`, `RulerControl.cs`, `VideoExporter.cs`, `GlobalHotkeyHook.cs`, `AppIdentity.cs`
- Standaardbestandsnamen "Knipsel" en "Screencast" volgen de taal (Knipsel, Snip, Ausschnitt, Capture, Recorte, Ritaglio, Recorte). Een eigen naamsjabloon blijft ongewijzigd
- Release notes van GitHub blijven in de taal waarin ze geschreven zijn
**6. Project**: in [src/Kniip/Kniip.csproj](src/Kniip/Kniip.csproj) wordt `SatelliteResourceLanguages` `nl;en;de;fr;es;it;pt-BR` (voor de WPF-systeemteksten), en de `Description` wordt Engels.
**7. Installer** ([installer/Kniip.iss](installer/Kniip.iss)):
- In `[Languages]` komen `dutch`, `english` (`Default.isl`), `german`, `french`, `spanish`, `italian` en `brazilianportuguese` (`BrazilianPortuguese.isl`), met `ShowLanguageDialog=auto`
- De eigen teksten (taken, "kniip nu starten", groepnamen) gaan naar `[CustomMessages]` per taal en worden aangeroepen met `{cm:...}`
- De gekozen taal geeft de installer als eerste taal door aan de app, via `HKCU\Software\kniip\Language`, alleen als er nog geen instelling bestaat. Komt dit in de praktijk niet goed over, dan blijft `System` de standaard
**8. Documentatie**:
- `README.md` wordt Engels (internationaal standaard) met taallinks bovenaan; daarnaast `README.nl.md` (huidige tekst), `README.de.md`, `README.fr.md`, `README.es.md`, `README.it.md` en `README.pt.md`
- `CHANGELOG.md`: vanaf nu in het Engels; de bestaande geschiedenis blijft Nederlands. Een nieuwe entry voor deze versie meldt de taalondersteuning
## Controle
- Bouwen met `build.ps1`, daarna elke taal instellen en alle vensters doorlopen op ontbrekende sleutels (die zijn zichtbaar als lege tekst) en op afgekapte lange teksten (vooral Duits, Frans, Spaans, Italiaans en Portugees) in vaste breedtes (bijvoorbeeld `Width="150"` in de instellingen). Waar nodig wordt de breedte aangepast
- Een klein script in `tools/` vergelijkt de sleutels van de zeven taalbestanden met elkaar
@@ -0,0 +1,94 @@
---
name: Tekstballon en -wolk
overview: 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.
todos:
- id: model
content: TextShape enum, StyleSet.TextShape en StyleProps.TextShape toevoegen in StyleSet.cs
status: completed
- id: geometry
content: "TextAnnotation: TailTip, BuildShape (kader/ballon/ovaal/wolk), Bounds/HitTest/Move/handles"
status: completed
- id: resize
content: "TextAnnotation: BoxWidth/BoxHeight, Box/TextArea, 8 formaatgrepen, woordafbreking, dubbelklik = automatisch; TextBox-editor op TextArea met vaste breedte"
status: completed
- id: ui
content: Vorm-keuzelijst + template in EditorWindow.xaml, wiring in UpdatePropsPanel en TextShape_Changed
status: completed
- id: create
content: Standaard staartpunt bij nieuwe tekst; omtrek+staart zichtbaar tijdens bewerken
status: completed
- id: verify
content: "Bouwen en handmatig testen: vormen wisselen, staart slepen, verplaatsen, kopiëren/plakken, export"
status: completed
isProject: 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](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](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](src/Kniip/Editor/Annotations/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](src/Kniip/Editor/EditorWindow.xaml) en [src/Kniip/Editor/EditorWindow.xaml.cs](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.
```mermaid
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).
+85
View File
@@ -0,0 +1,85 @@
---
name: Vormenknop
overview: Een nieuwe werkbalkknop "Vormen" naast Rechthoek en Ellips, met in de eigenschappenbalk een keuzelijst voor 11 vormen (driehoek, pijl, ster, wolk, ballon, hart, maan, druppel, ei, bliksemschicht, spiraal). Je tekent een vorm door een kader te slepen, net als bij een rechthoek.
todos:
- id: model
content: ToolKind.Shape, ShapeKind-enum, StyleSet.ShapeKind en StyleProps.ShapeKind in StyleSet.cs
status: completed
- id: geometries
content: "ShapeGeometries.cs: de 11 vormen in een eenheidsvierkant, plus IsOpen en Icon; ShapeIconConverter"
status: completed
- id: annotation
content: "FigureAnnotation.cs: Render met transform, HitTest (open/gesloten), spiegelen"
status: completed
- id: ui
content: Werkbalkknop, ShapeKindTemplate, ShapeGroup/ShapeKindCombo in EditorWindow.xaml
status: completed
- id: wiring
content: "EditorWindow.xaml.cs: CreateAnnotation, UpdatePropsPanel, ShapeKind_Changed, knopicoon, DefaultSplitOrder, sneltoets F, hinttekst"
status: completed
- id: verify
content: "Bouwen en handmatig testen: alle vormen tekenen, wisselen, Shift, grepen, spiegelen, kopiëren/plakken, export"
status: completed
isProject: false
---
# Vormenknop met 11 vormen
## Gedrag
- Er komt een nieuwe gereedschapsknop **Vormen** direct na Ellips. Rechthoek en Ellips blijven zoals ze zijn.
- In de eigenschappenbalk staat een keuzelijst **Vorm** (icoon plus naam): Driehoek, Pijl, Ster, Wolk, Ballon, Hart, Maan, Druppel, Ei, Bliksemschicht, Spiraal.
- Het icoon op de werkbalkknop toont de laatst gekozen vorm. De keuze wordt per gereedschap onthouden, net als de andere stijlinstellingen.
- Tekenen gaat door een kader te slepen; de vorm vult dat kader. Met Shift wordt het kader vierkant. Er zijn 8 formaatgrepen, net als bij een rechthoek.
- Eigenschappen: randkleur, vulkleur, dikte, stippellijn en schaduw. De spiraal is een open lijn en krijgt geen vulling.
- Bij een geselecteerde vorm kun je via de keuzelijst een andere vorm kiezen (met undo).
- Spiegelen (horizontaal en verticaal) werkt voor vormen, zodat de pijl, maan, bliksem, ballon en spiraal ook de andere kant op kunnen.
- Sneltoets **F** (figuur), want de meeste voor de hand liggende letters zijn al bezet.
- Aanname: **Ballon** is hier een tekstballon zonder tekst (afgeronde rechthoek met een staart linksonder) en **Wolk** een gedachtewolk zonder staart, als losse vorm. Voor een ballon of wolk met tekst blijft het tekstgereedschap bestaan.
## Technische aanpak
**1. Model** in [src/Kniip/Editor/StyleSet.cs](src/Kniip/Editor/StyleSet.cs)
- `ToolKind.Shape` achteraan toevoegen. Instellingen worden als tekst opgeslagen (`JsonStringEnumConverter`), dus de volgorde breekt niets.
- Nieuwe enum `ShapeKind { Triangle, BlockArrow, Star, Cloud, Balloon, Heart, Moon, Drop, Egg, Lightning, Spiral }`.
- `StyleSet.ShapeKind` (standaard `Triangle`) en vlag `StyleProps.ShapeKind`.
- `PropsFor(ToolKind.Shape)` = Stroke, Fill, Thickness, Dash, Shadow, ShapeKind. `DefaultFor` gebruikt de gewone standaard (rode rand, geen vulling).
**2. Geometrie** in een nieuw bestand `src/Kniip/Editor/Annotations/ShapeGeometries.cs`
- `Geometry For(ShapeKind kind)` bouwt elke vorm in een eenheidsvierkant (0..1), gecachet en bevroren, zoals [CursorGeometries.cs](src/Kniip/Editor/Annotations/CursorGeometries.cs):
- Driehoek, Pijl (blokpijl naar rechts), Ster (5 punten), Bliksemschicht: rechte polygonen.
- Hart, Druppel, Ei, Maan, Ballon: Bezier- en boogsegmenten in een `StreamGeometry`. De maan wordt een cirkel min een verschoven cirkel (`GeometryCombineMode.Exclude`).
- Wolk: een vereniging van cirkels, zoals `TextAnnotation` dat al doet.
- Spiraal: een open polyline langs een Archimedes-spiraal (ongeveer 3 omwentelingen).
- `bool IsOpen(ShapeKind)` geeft true voor Spiraal.
- `Geometry Icon(ShapeKind)` schaalt dezelfde vorm naar 20x20 voor de knop en de keuzelijst. Er komt een `ShapeIconConverter` naar het voorbeeld van [CursorIconConverter.cs](src/Kniip/Editor/CursorIconConverter.cs).
**3. Annotatie**: nieuw `src/Kniip/Editor/Annotations/FigureAnnotation.cs`, afgeleid van [RectAnnotation.cs](src/Kniip/Editor/Annotations/RectAnnotation.cs), zodat grepen, verplaatsen en Shift al werken.
- `Kind => ToolKind.Shape`. De vorm komt uit `Style.ShapeKind`, zodat wisselen via `ChangeStyle` meteen werkt.
- `Render`: de eenheidsgeometrie klonen met een `MatrixTransform` naar `Rect` (plus een spiegeling met `FlipX`/`FlipY`). De lijndikte schaalt daardoor niet mee. Tekenen gebeurt via `RenderHelpers.DrawWithShadow`, zonder vulling bij een open vorm.
- `HitTest`: `FillContains` voor gesloten vormen, `StrokeContains` met tolerantie voor de spiraal.
- `CanFlip => true`; `Flip` wisselt `FlipX` of `FlipY`.
**4. Editor** in [src/Kniip/Editor/EditorWindow.xaml](src/Kniip/Editor/EditorWindow.xaml) en [EditorWindow.xaml.cs](src/Kniip/Editor/EditorWindow.xaml.cs)
- XAML: een `RadioButton Tag="Shape" ItemId="Shape" Title="Vormen"` na Ellips, met een `Path x:Name="ShapeToolIcon"`. Daarnaast een `ShapeKindTemplate` (icoon via de converter, plus een label per waarde, net als `CursorKindTemplate`) en een `ShapeGroup` met `ShapeKindCombo` in de eigenschappenbalk.
- Code:
- `ShapeKindValues` als `ItemsSource`.
- `"Shape"` na `"Ellipse"` in `DefaultSplitOrder`. `MergeToolbarOrder` zet de nieuwe knop bij bestaande gebruikers automatisch na Ellips.
- `CreateAnnotation`: `ToolKind.Shape => new FigureAnnotation { Rect = new Rect(p, p), Style = style }`.
- `UpdatePropsPanel`: zichtbaarheid van `ShapeGroup` en selectie van `ShapeKindCombo`. De vulling wordt verborgen bij Spiraal.
- Handler `ShapeKind_Changed` roept `ChangeStyle("shape", s => s.ShapeKind = kind)` aan en werkt `ShapeToolIcon` bij. Bij het starten wordt het icoon ook gezet.
- Sneltoets `Key.F` en een hinttekst: "Sleep om de gekozen vorm te tekenen. Shift = gelijke breedte en hoogte. Kies een andere vorm in de balk eronder."
```mermaid
flowchart LR
ShapeKindCombo --> ChangeStyle --> StyleShapeKind["StyleSet.ShapeKind"]
StyleShapeKind --> ShapeGeometries
ShapeGeometries --> FigureAnnotation
ShapeGeometries --> ShapeIconConverter
ShapeIconConverter --> ShapeToolIcon
ShapeIconConverter --> ShapeKindTemplate
```
## Buiten scope
- Draaien van vormen. Een pijl omhoog of omlaag kan daardoor niet; links en rechts wel, via spiegelen.
- Tekst in een vorm. Daarvoor blijft het tekstgereedschap met ballon en wolk.
- Instelbare details per vorm, zoals het aantal punten van de ster, de dikte van de maan of het aantal windingen van de spiraal.