Zum Inhalt

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-bleed MetalView + Topbar-Overlay + Fullscreen-Exit-Overlay (wie heute).
  • EditorPresenter (ViewModifier): kapselt die Modus-Verzweigung — bei .inspector hängt es .inspector(isPresented:) an (heutiges Verhalten, mit Ein-/Ausklapp-Toggle); bei .sheet hä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; showEditor bleibt 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: showEditor wird 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 einem didInit-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; xcodebuild macOS 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; showEditor als 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 muss nil als macOS behandeln (via #if os(macOS) den Pfad fest auf .inspector setzen, statt sich auf die Env-Var zu verlassen).