Physarum Native — Adaptive Compact UI (iPad/iPhone) Design Spec¶
Status: Approved · 2026-06-03 · author: Johannes (brainstormed with Claude) Builds on:
2026-06-03-authoring-editor-design.md§5 (adaptive editor layout). That spec decided "Live-Player immer sichtbar; Editor adaptiv:.inspector(regular) ↔ Bottom-Sheet (compact)". This spec makes that concrete for native and refines the iPad behaviour.
1. Kontext & Ziel¶
Die native App spielt + editiert heute nur auf macOS (Inc 3a/3b: RootView nutzt .inspector
+ .sheet unbedingt, ohne Size-Class-Adaptivität). Die Engine-/Render-Pipeline ist bereits
iOS-bereit (MetalView hat den UIViewRepresentable-Pfad via #if os(macOS)/else). Dieser
Increment macht RootView adaptiv, sodass der volle Authoring-Editor (alle Plan-3-Features)
auf iPhone + iPad in beiden Orientierungen idiomatisch nutzbar ist — über ein SwiftUI-Codebase.
tvOS bleibt Playback-only (out of scope).
2. Festgezurrte Entscheidungen (Brainstorming 2026-06-03)¶
| Achse | Entscheidung |
|---|---|
| iPad-Modus | Adaptiv nach Orientierung: iPad-Landscape → Inspector (Authoring), iPad-Portrait → Bottom-Sheet (Ambiance). |
| iPhone | Immer Bottom-Sheet. |
| macOS | Immer Inspector (unverändert). |
| Player | Läuft immer vollflächig weiter (full-bleed MetalView); das Sheet liegt nur darüber — das „lebende Bild" bleibt sichtbar (presentationBackgroundInteraction(.enabled)). |
| Editor-Inhalt | Unverändert — dieselbe EditorView (Toolbar + Sektionen + Plan-3-Features) in beiden Präsentationsmodi. |
| Was sich NICHT ändert | Engine, Render-Pipeline, RenderCoordinator, alle Plan-3-Features, der Editor-Inhalt. Nur RootView (Präsentation) + ein paar Topbar-Details. |
3. Kern: die EditorPresentation-Entscheidung (reine, testbare Funktion)¶
Der einzige neue „Logik"-Kern — alles andere ist SwiftUI-Verdrahtung. Eine reine Funktion mappt (Plattform, Size-Class, Geometrie) → Präsentationsmodus, damit sie unit-testbar ist:
enum EditorPresentation { case inspector, sheet }
/// size: the available layout size (from a GeometryReader).
/// hSizeClass: nil on macOS; .compact/.regular on iOS.
func editorPresentation(hSizeClass: UserInterfaceSizeClass?, size: CGSize) -> EditorPresentation
Regel:
- macOS (hSizeClass == nil) → .inspector.
- compact (hSizeClass == .compact — iPhone, iPad Split-View/Slide-Over) → .sheet.
- regular (iPad full-screen): size.width > size.height (Landscape) → .inspector; sonst (Portrait/quadratisch) → .sheet.
Begründung für Geometrie statt UIDevice.orientation: iPad meldet im Vollbild beide
Orientierungen als .regular; nur das Seitenverhältnis der verfügbaren Fläche unterscheidet sie
zuverlässig (und folgt Multitasking-Resizes korrekt).
4. RootView-Architektur¶
RootView umbaut auf eine GeometryReader-Hülle, die editorPresentation(...) berechnet und je
nach Ergebnis genau einen der zwei Präsentations-Modifier aktiviert:
struct RootView: View {
@State private var state = AppState()
@State private var showEditor = true // inspector visible OR sheet presented
@State private var showLibrary = false
@State private var fullscreen = false
@Environment(\.horizontalSizeClass) private var hSizeClass
var body: some View {
GeometryReader { geo in
let mode = editorPresentation(hSizeClass: hSizeClass, size: geo.size)
player
.modifier(EditorPresenter(mode: mode, isPresented: $showEditor, state: state, fullscreen: fullscreen))
// topbar / fullscreen / library as today, adapted (see below)
}
}
}
player: der full-bleedMetalView+ Topbar-Overlay + Fullscreen-Exit-Overlay (wie heute).EditorPresenter(ViewModifier): kapselt die Modus-Verzweigung — bei.inspectorhängt es.inspector(isPresented:)an (heutiges Verhalten, mit Ein-/Ausklapp-Toggle); bei.sheethängt es.sheet(isPresented:)mit.presentationDetents([.medium, .large])+.presentationBackgroundInteraction(.enabled)+.presentationContentInteraction(.scrolls)an. Im Fullscreen-Modus präsentiert es nichts (Editor aus).- Modus-Wechsel zur Laufzeit (z.B. iPad-Rotation): SwiftUI re-evaluiert
body, der andere Modifier wird aktiv;showEditorbleibt erhalten, also bleibt der Editor „offen" über den Wechsel — nur die Hülle ändert sich (Sheet ↔ Inspector). - Initialer Zustand: Inspector-Modus startet expandiert (regular: Mac + iPad-Landscape,
Authoring-first); Sheet-Modus startet geschlossen (compact + iPad-Portrait → player-first,
das „lebende Bild" zuerst). Konkret:
showEditorwird beim ersten Erscheinen einmalig aus dem initial berechneten Modus gesetzt (mode == .inspector→ true,.sheet→ false). Der Plan legt den exakten Mechanismus fest (z.B.onAppear-Einmal-Init mit einemdidInit-Flag, damit spätere Rotationen den vom Nutzer gewählten Zustand nicht überschreiben).
5. Topbar-Adaptivität¶
Die schwebende Glass-Topbar (PHYSARUM-Eyebrow + Preset-Name + Icons) bleibt in allen Modi sichtbar
über dem Player. Anpassungen:
- Das Editor-Toggle-Icon (heute sidebar.right) toggelt showEditor — was im aktiven Modus das
Inspector-Panel ein-/ausklappt oder das Sheet zeigt/versteckt. (Optional ein modus-passendes
SF-Symbol: sidebar.right für Inspector, slider.horizontal.3 für Sheet — kosmetisch.)
- Auf schmalen iPhones muss die Topbar in eine Zeile passen (Eyebrow + Name truncatable + 3 Icons).
Falls zu eng: Eyebrow „PHYSARUM" auf compact ausblenden.
6. Sheet-Stacking auf compact¶
Drei Sheet-Quellen koexistieren: Editor-Sheet (persistent, mit Detents), Library, MorphSheet, SaveDialog.
SwiftUI erlaubt pro Präsentator nur ein .sheet gleichzeitig.
- Editor-Sheet = die persistente Editier-Fläche (RootView-Präsentator, Detents + Background-Interaction).
- Morph + Save werden aus der EditorView heraus präsentiert (also innerhalb des Editor-Sheets)
→ stapeln natürlich darüber (iOS erlaubt Sheet-über-Sheet). Heutiges Verhalten bleibt.
- Library wird aus der Topbar präsentiert (anderer Präsentator als das Editor-Sheet). Um die
Same-Presenter-Kollision zu vermeiden: auf compact schließt das Öffnen der Library zuerst das
Editor-Sheet (showEditor = false), dann erscheint die Library. Nach Preset-Wahl kann der Nutzer
den Editor wieder öffnen. Auf regular (Inspector) gibt es keine Kollision — Library-Sheet über dem
Inspector ist ok.
7. Fullscreen auf compact¶
Wie auf dem Mac: Fullscreen-Toggle (Topbar-Button; Hotkey C nur macOS) → Topbar + Editor aus, Player
formatfüllend; Tap/„X"-Button (bzw. Esc auf Mac) zurück. Auf iOS keine native Fullscreen-API nötig —
es ist ohnehin formatfüllend; „Fullscreen" = Chrome ausblenden.
8. Engine / Rendering¶
Keine Änderung. MetalView rendert bereits auf iOS (UIViewRepresentable-Pfad). Der Player läuft im
Hintergrund weiter, während das Sheet darüber liegt — presentationBackgroundInteraction(.enabled)
hält den Player sichtbar und (falls gewünscht) interaktiv. field = 720 bleibt (quadratisches
Sim-Feld, aspect-fill in die View).
9. Testing & Verifikation¶
- Unit-testbar (App-Test-Target):
editorPresentation(...): macOS→inspector; compact→sheet; regular+landscape→inspector; regular+portrait→sheet; quadratisch (w==h)→sheet. Tabellengetrieben.- Build + visuell (Johannes):
- iOS-Simulator: iPhone (Sheet, Detents, Player läuft dahinter), iPad-Portrait (Sheet), iPad-Landscape (Inspector), Rotation iPad (Sheet↔Inspector live, Editor bleibt offen).
- Echtes Gerät (iPhone + iPad, Free-Provisioning): Touch-Ergonomie der Plan-3-Controls
(Slider tap-to-type, 🔒-Toggles, Randomize/Morph), Sheet-Detents fühlen sich gut an,
Performance des Live-Renders hinter dem
.medium-Sheet. - Gate:
swift/app-Suite grün;xcodebuildmacOS BUILD SUCCEEDED und iOS-Sim BUILD SUCCEEDED; App startet auf iPhone+iPad-Sim, Editor präsentiert sich pro Orientierung korrekt.
10. Scope (in) / Out-of-scope¶
In: adaptive EditorPresentation-Funktion (testbar) · RootView-Umbau (GeometryReader +
EditorPresenter-Modifier) · Bottom-Sheet mit Detents + Background-Interaction · Topbar-Adaptivität
(Editor-Toggle, schmale iPhones) · Sheet-Stacking-Regel (Library schließt Editor-Sheet auf compact) ·
Fullscreen-as-chrome-hide auf iOS · iOS-Sim-Build grün · Unit-Test der Entscheidungsregel.
Out-of-scope (spätere Increments): tvOS-UI (Playback-only bleibt) · iPad-Pencil/Trackpad-Feinheiten · Slider Shift/Alt-Modifier (Mac-only, eh deferred) · Multitasking-Edge-Cases jenseits der Size-Class-Regel · Capture/Export · Audio · CloudKit · DMG-Cutover. Der Editor-Inhalt ändert sich nicht (das ist Plan 3).
11. Offene Risiken / im Plan zu klären¶
presentationBackgroundInteraction+ MTKView-Performance: Live-Render hinter einem.medium-Sheet auf einem echten iPhone — messen; ggf. bei aktivem Sheet die Render-Cadence drosseln (nicht in v1, nur falls nötig).- Modus-Wechsel-State: beim Rotations-Wechsel Sheet↔Inspector darf der Editor weder doppelt
präsentiert werden noch flackern;
showEditorals einzige Quelle der Wahrheit testen. - Sheet-über-Sheet (Morph/Save über Editor-Sheet) auf iOS verifizieren (Detents-Interaktion).
- Topbar-Truncation auf den schmalsten iPhones (SE) — visuell prüfen.
@Environment(\.horizontalSizeClass)auf macOS ist nicht verfügbar/nil— die Funktion mussnilals macOS behandeln (via#if os(macOS)den Pfad fest auf.inspectorsetzen, statt sich auf die Env-Var zu verlassen).