Status: Partially shipped — ~33 controls built (all pure composites); Camera family core landed
but not device-verified. See the "STILL OPEN" and "SKIPPED" lists below for what remains.
· Date: 2026-07-19 (status refreshed 2026-07-19)
Save to (repo convention): plans/controls-library-plan.md
Depends on: Plan 1 — Missing framework features. Each wave below
names the Plan-1 features (F1–F11) it needs; a wave cannot start before its features land.
SwiftDotNet.Controls— ~33 controls built, all pure composites, 94 tests green. Pills, badges, skeleton, progress, FABs; Toast/Dialog/Loading/FloatingPanel (over the F2 overlay layer); Slider/ RangeSlider/SecurityPin/TextEntry/AutoComplete/CountryPicker; TableView(+Cells)/TreeView/DataGrid; Scheduler (calendar+agenda); StaggeredGrid/CarouselGallery/ReorderableList; ImageViewer, ColorPicker, FrostedGlassView, ChatView (no attachments).Camera family (Wave 6) — core landed:
SwiftDotNet.Controls.Camera(CameraViewCustomView +CameraAnalyzersflags +BarcodeFormat+ event grammar, buildable+tested),.Camera.Apple(registration P/Invoke),native/camera/CameraRenderer.swift(AVFoundation preview + Vision barcode/face/text, type-checked vs the iOS SDK). The ShinyCamera.Barcode/.Face/.Ocr/.Motionpackage split is folded into oneCameraAnalyzersflag set. Not runtime-verified — needs a device.SKIPPED (per user — descoped from this plan):
- SignaturePad, ImageEditor — need F8 (drawing canvas). Descoped.
- MediaPickerButton, AddressEntry, ChatView attachments — need F10 (platform services). Descoped.
- Camera.Documents / Camera.Ai (credit-card/business-card/AAMVA parsers) — descoped; barcode scanning is the camera-analysis scope kept.
STILL OPEN (not skipped, not yet done):
- VirtualizedGrid — needs F7(d) true windowing (native lists already recycle; only C# build is O(n)).
- ParallaxCollectionView — needs F11 (scroll-offset geometry), which is deferred.
- BorderlessEntry — needs a clear/alpha background primitive.
- FontPicker / FontSizePicker — SwiftDotNet fonts are semantic tokens, not point sizes (needs a design call).
- ~8 remaining Cell types — built 6 of 14 (Label/Switch/Button/Entry/Checkbox/Navigation); missing Radio, Command, Custom, SimpleCheck + the Date/Time/Number/Text/Duration picker cells.
- Camera.Motion, and the Compose (CameraX) / WinUI camera renderers (only the Swift renderer exists).
NEXT: Skia backend focus (per user).
Port the controls from ~/Desktop/dev/controls into SwiftDotNet as a separate control library,
mirroring the existing SwiftDotNet.Maps split exactly.
Scope of this effort — two package families only:
Shiny.Maui.Controls(the main package) — pills, badges, toasts, floating panels, trees, tables, sliders, color picker, image viewer, scheduler, chat, … (Waves 0–5 below).Shiny.Maui.Camera.*— the camera family:Camera(coreCameraView+ native handler),Camera.Barcode,Camera.Face,Camera.Ocr,Camera.Motion,Camera.Documents,Camera.Ai, plusShiny.Controls.Camera.Shared(Wave 6).
Explicitly out of this effort (not "first wave" — not in scope at all here; each needs its own later
analysis): Shiny.Maui.Controls.SpeechAddins, .Markdown, .MermaidDiagrams, .Desktop, the standalone
.Barcodes (ZXing generation — distinct from camera barcode scanning, which is in via Camera.Barcode),
.Themes.Material/Ocean, .Kiosk, and the entire parallel Shiny.Blazor.Controls family.
Why a separate library. Same reasons as Maps: the core stays dependency-free net10.0 and
referenceable from every head; the few controls that need native/heavy renderers live in companion
packages so a neutral consumer isn't forced to pull them. The camera family in particular is native +
platform-ML heavy, so it lives in its own companion packages (§Wave 6).
The porting model — two shapes, matching Core's two extension points:
- Composite (
View+Body) — theRatingpattern. The Shiny control's constructor tree ofLabel/Border/Image/Gridbecomes a SwiftDotNet view tree; itsBindablePropertys become constructor args +State<T>; itspropertyChangedmutations become ordinary re-render. Renders on every backend with zero native code. This is ~80% of the port. - Custom native primitive (
CustomView+ per-backend renderer) — theMappattern. Only for the handful needing native rendering (blur, native-recycling grids, freeform drawing). Costs a renderer in each backend.
Most controls are shape #1 and are gated only by the framework features in Plan 1, not by native code — which is why Plan 1 comes first.
src/SwiftDotNet.Controls/ net10.0, dependency-free, RootNamespace=SwiftDotNet
→ composites (View subclasses) + CustomViews + the imperative service surfaces
(references only ..\SwiftDotNet)
src/SwiftDotNet.Controls.Apple/ native renderers for the CustomViews that need them
src/SwiftDotNet.Controls.Web/ Blazor renderers (mirror SwiftDotNet.Maps.Web)
(+ Gtk/Windows/Skia renderers where a CustomView needs them)
native/controls/*.swift / *.kt native renderers for SwiftUI/Compose CustomViews (blur, grids)
# Camera family (Wave 6) — its own packages so a controls consumer isn't forced to pull camera/ML deps
src/SwiftDotNet.Controls.Camera/ net10.0 — CameraView (a CustomView) + CameraPhoto/CameraInfo +
the IFrameAnalyzer pipeline abstractions (from Shiny.Controls.Camera.Shared). No native code.
src/SwiftDotNet.Controls.Camera.Apple/ native AVFoundation preview + Vision analyzers (renderer + services)
native/camera/*.swift / *.kt native camera-preview renderers for the SwiftUI/Compose backends
(AVCaptureVideoPreviewLayer / CameraX PreviewView) + MLKit/Vision frame analyzers
src/SwiftDotNet.Controls.Camera.{Ai,Barcode,Documents,Face,Motion,Ocr}/
net10.0 analyzer add-ons that plug into the frame pipeline (map to Vision / ML Kit per platform)
sample/SharedUI/ add a "Controls" tour tab exercising each wave
SwiftDotNet.slnx add the new projects
Composite controls need no companion package — they live entirely in SwiftDotNet.Controls
(net10.0) and render everywhere. Companions exist only for the native CustomViews (Waves 4–6). The
camera family is all native/ML and therefore ships entirely as its own packages.
Tiers and machinery are from the source-repo inventory. "Approach" = SwiftDotNet shape; "Needs" = Plan-1 features that gate it.
| Control | Tier | Approach | Needs (Plan 1) |
|---|---|---|---|
| PillView, BadgeView | 1 | Composite | F4 (pulse) |
| Skeleton, ProgressBar | 1 | Composite | F4 loop-anim, F5 gradient |
| BorderlessEntry, TextEntry, AutoCompleteEntry | 1 | Composite | F9 focus/keyboard |
| SecurityPin | 1 | Composite | F9 |
| Cells (14) + TableView | 1 | Composite | F7(a–c) sections/swipe/reorder, F9 |
| TreeView | 1 | Composite | F7(a–c), per-child state (F-note) |
| DataGrid | 1 | Composite | F7(d) virtualization (large-N), F11 |
| CountryPicker, FontPicker, DurationPicker | 1 | Composite | F2 overlay (picker page/panel), F9 |
| Slider, RangeSlider | 1 | Composite | F1 pan, F11 geometry |
| Fab, FabMenu | 1 | Composite | F4 transforms/anim |
| Scheduler (3 views) | 1 | Composite | F7(a) sections, F11 geometry, F3 |
| ParallaxCollectionView | 1 | Composite | F7, F11 |
| Toast, Dialogs, Overlay/LoadingOverlay | 1/2 | Composite + service | F2 overlay host, F4 |
| OverlayHost / ShinyContentPage | infra | maps onto F2 | F2 |
| FloatingPanel | 2 | Composite | F1 pan, F2 overlay, F11 |
| ImageViewer | 2 | Composite | F1 pinch/pan, F3 images, F2 |
| FrostedGlassView | 2 | CustomView (native) | F6 blur |
| ColorPicker | 2 | Composite (gradients) | F5 gradient, F1 |
| VirtualizedGrid, StaggeredGrid, CarouselGallery | 2 | CustomView (native recycling) | F7(d–e) |
| SignaturePad | 2/3 | CustomView (drawing) | F8 canvas, F1 |
| ImageEditor | 3 | CustomView (drawing) | F8, F1, F3, F10 export |
| ChatView | 3 | Composite (large) | F7, F3, F9, F10 attach |
| MediaPickerButton | 3 | Composite + service | F10 media picker |
| AddressEntry | 3 | Composite + service | F10 (HTTP geocode) |
| CameraView (Camera core) | 3 | CustomView (native preview) | F10 camera+permissions, F1 tap-focus, F3 |
| Camera.Barcode / .Face / .Ocr / .Motion | 3 | Frame-analyzer add-on → platform ML | F10 (Vision / ML Kit) |
| Camera.Documents / .Ai | 3 | Analyzer + pure-C# parsers over the ML text/detections | F10 |
Out of scope for this plan (each needs its own later analysis): Shiny.Maui.Controls.SpeechAddins,
.Markdown, .MermaidDiagrams, .Desktop, the standalone .Barcodes (ZXing generation — camera barcode
scanning is in, via Camera.Barcode), .Themes.Material/Ocean, .Kiosk, and the whole parallel
Shiny.Blazor.Controls family.
- Create
src/SwiftDotNet.Controls(net10.0,RootNamespace=SwiftDotNet, ProjectReference to..\SwiftDotNet,IsTrimmable) — copySwiftDotNet.Maps.csprojverbatim. - Add to
SwiftDotNet.slnx. Add a Controls tab tosample/SharedUI/ContentView.cs. - Port a theme-token shim: the Shiny controls read
ShinyThemeKeysviaSetDynamicResource; map those to SwiftDotNet's environment cascade /Theme(Core/Theme.cs). - First control (proof):
PillView— pure composite (Border+Text, 6 semantic types). Ships now, validates the whole pipeline end-to-end on all backends.
Badges, chips, skeletons, bars, FABs — the visual-only controls.
- PillView, BadgeView (corner badge wrapping content, dot/count/overflow, pulse via F4).
- Skeleton/SkeletonView (shimmer via F4 loop + F5 gradient), ProgressBar (F4).
- Fab, FabMenu (radial expand via F4 transforms), FabMenuItem.
- BorderlessEntry (trivial styled field).
- Ends: a "Badges & FABs" sample section.
The overlay family — the biggest single unlock.
- Implement OverlayHost / ShinyContentPage as the app's Plan-1 F2 overlay layer (this is the infra the rest sit in).
- Toast (
IToastService.Show(...), queue/stack, auto-dismiss, spinner/progress, pill/fill) — a code-invoked service over F2. - Dialogs (
IDialogServicealert/confirm/prompt), Overlay/LoadingOverlay (uses F6 blur when available, tint fallback until then). - FloatingPanel (detent bottom/top sheet, drag handle, pan-to-resize via F1, keyboard avoidance).
- Ends: a "Overlays" sample section (toast button, confirm dialog, floating panel).
The form/collection tier.
- Slider, RangeSlider (pan via F1, thumb position via F11), SecurityPin, TextEntry, AutoCompleteEntry (F9 focus/keyboard + F2 dropdown).
- Cells (14) + TableView (grouped table via F7 sections, swipe actions, drag-reorder), TreeView (expand/collapse, lazy load, reorder).
- CountryPicker, FontPicker/FontSizePicker, DurationPicker (picker pages/panels over F2).
- Ends: a "Forms & Lists" sample section.
- ImageViewer (thumbnail → fullscreen pinch/pan/double-tap zoom via F1 + F3, presented via F2).
- ColorPicker (HSB spectrum + hue bar built from F5 gradients — no drawing canvas needed —
- opacity + hex).
- FrostedGlassView — first CustomView with native renderers (F6): companion renderers in
SwiftDotNet.Controls.Apple(UIVisualEffectView),native/controls/*.kt(RenderEffect), WinUI Acrylic, Webbackdrop-filter, Skia blur pass; GTK tint fallback. - Scheduler (calendar grid / agenda / list — composites over F7 + F11 + F3).
- DataGrid (sortable/filterable/groupable — composite; F7(d) virtualization for large N).
- Ends: an "Images & Scheduler" sample section.
The Tier-2/3 controls that require native recycling, freeform drawing, or platform services.
- VirtualizedGrid, StaggeredGrid, CarouselGallery —
CustomViews backed by native recycling views (RecyclerView / UICollectionView / ScrollViewer), or a windowed engine list where a native handler isn't warranted. Companion renderers per backend. - SignaturePad —
DrawingCanvasCustomView (F8), Skia-backend-first (the engine already owns a canvas), then native renderers; PNG export via F10. - ImageEditor — largest: F8 canvas + crop/rotate/annotate + undo/redo + F10 export. Skia-first.
- ChatView — large composite (bubbles/typing/receipts/templates) over F7 + F3 + F9, attach via F10.
- MediaPickerButton, AddressEntry — thin composites over F10 services.
- Ends: a "Drawing & Chat" sample section, with clear per-backend capability notes (some are Skia/native-only).
The Shiny.Maui.Camera.* family. This is all native + platform ML — it ships as its own packages
(§Project structure) and does not affect the pure-composition controls above.
- New capability first (a Plan-1 addition): a live camera-preview
CustomView— theMapmodel applied to a camera.CameraViewemits a"CameraView"node; each backend registers a renderer that hosts the native preview (SwiftUIAVCaptureVideoPreviewLayer, Compose CameraXPreviewView, WinUIMediaPlayerElement/CaptureElement, WebgetUserMedia+<video>; GTK/Skia → placeholder). Capture, torch, lens-switch, and tap-to-focus ride the event channel (F1) with akind:bodygrammar like Map's. Track this as an addendum to Plan 1 (call it F12 — camera preview); it is camera-specific, so it lives with this wave rather than the general feature set. - Camera core (
SwiftDotNet.Controls.Camera):CameraView(CustomView) +CameraPhoto/CameraInfo- the
IFrameAnalyzerpipeline abstractions (portShiny.Controls.Camera.Shared— frame model, coordinate transforms, overlay boxes). A captured photo is shown via F3 raster images.
- the
- ML analyzer add-ons (each its own package, plugging into the frame pipeline, mapped to the platform
vision stack — Apple Vision / Android ML Kit):
- Camera.Barcode — barcode/QR scanning.
- Camera.Face — face detection.
- Camera.Ocr — text recognition.
- Camera.Motion — frame-difference motion detection (mostly pure C# over frames).
- Camera.Documents — business-card / credit-card / drivers-license parsing (pure-C# parsers over OCR text — the analyzers are native, the parsers port straight across).
- Camera.Ai — document analysis over the detections.
- Reality check: the preview + analyzers are native-only and land first on the backends that have
a camera (iOS/Android via the shims; macOS/Windows where a capture API exists). GTK/Skia/Web-without-
camera show the
⚠️placeholder. Permissions/capture are F10 platform services. - Ends: a "Camera" sample section (live preview + a barcode/OCR scan), gated to camera-capable backends.
- Multiplexed events: controls with several callbacks use one event channel + a
kind:bodyvalue grammar — copyMap.Dispatch:61. - Rich payloads as string props: any structured config (theme tokens, gradient stops, detents,
cell models) serializes to a JSON string prop — copy
MapJson. - Composite =
View.Body: copyRatingfor every Tier-1 control. - CustomView + registry: copy
Map+ the*Renderers.Registerseams for Wave 4–5 natives. - Service registration: an
AddSwiftDotNetControls()extension registeringIToastService/IDialogService/IOverlayService, mirroringMauiAppBuilderExtensions.csin the source repo.
- New (Waves 0–5):
src/SwiftDotNet.Controls/**,src/SwiftDotNet.Controls.Apple/**,src/SwiftDotNet.Controls.Web/**,native/controls/**, additions tosample/SharedUI/ContentView.cs,SwiftDotNet.slnx, a README backend/library row. - New (Wave 6 — camera):
src/SwiftDotNet.Controls.Camera/**,src/SwiftDotNet.Controls.Camera.Apple/**,src/SwiftDotNet.Controls.Camera.{Ai,Barcode,Documents,Face,Motion,Ocr}/**,native/camera/**. - Read/copy patterns from:
src/SwiftDotNet.Maps/{Map,MapJson,MapTypes}.cs,src/SwiftDotNet.Maps.Web/MapLibreMap.cs,sample/SharedUI/Rating.cs,src/SwiftDotNet/Core/{CustomView,View,State,Theme}.cs. - Source to port:
~/Desktop/dev/controls/src/Shiny.Maui.Controls/**(Waves 0–5) and~/Desktop/dev/controls/src/Shiny.Maui.Controls.Camera*/**+Shiny.Controls.Camera.Shared/**(Wave 6).
- Per wave: build
src/SwiftDotNet.Controls(+ any companion) and the sample on macOS; run the new sample tab and screenshot each control on SwiftUI (macOS), GTK, Web, Skia (the four buildable here). - Parity check: where feasible, screenshot the same control in the source Shiny sample app and diff structure/behavior (not pixels — SwiftDotNet is native-look per backend, except Skia which is uniform).
- Interaction: pill/badge render (W1); toast shows+auto-dismisses, dialog confirms, floating panel drags to detents (W2); slider pans, table swipes+reorders, tree expands (W3); image viewer pinch-zooms, color picker updates hex (W4); signature captures a stroke on Skia, chat scrolls+loads-more (W5); live camera preview shows + a barcode/OCR scan fires its callback on a camera-capable backend (W6).
- Composite = zero native: confirm every Tier-1 control renders on all four backends without a registered renderer (proves it decomposed to primitives).
- Graceful degradation: a native-only control (FrostedGlass on GTK, SignaturePad off-Skia before its
renderer lands) shows the tint fallback /
⚠️placeholder, never a crash.