2026-04-08

Interaktion, Rendering und Zustand

Bei interaktiver Software liegt die eigentliche Arbeit nicht bei Farben, AbstĂ€nden oder Widget-Namen. Die harten Probleme beginnen dort, wo ein System gleichzeitig Geometrie zeichnen, Eingaben auswerten, ZustĂ€nde halten, Text editieren, Auswahl sichtbar machen und Änderungen wieder zurĂŒcknehmen muss. Genau dort trennt sich eine bloße OberflĂ€che von einem Werkzeug.

nkrunner arbeitet in diesem Bereich. Der Kern liegt nicht in einem Satz fertiger Controls, sondern in den technischen Schichten darunter: Objektmodell, Eingabeverarbeitung, Picking, Text, Shader, Pipeline-ZustÀnde, Historie und NebenlÀufigkeit. Diese Dinge greifen direkt ineinander. Sobald eine dieser Schichten unsauber wird, zerfÀllt das Verhalten an der OberflÀche.

Objektmodell und Eingabefluss

Ein Objekt in so einem System ist nicht nur Geometrie. Es braucht IdentitĂ€t, Auswahlzustand, Editierbarkeit und eine klare Beziehung zu Eingaben. Maus und Tastatur dĂŒrfen nicht als lokale SonderfĂ€lle wachsen. Hover, Selektion, Mehrfachselektion, Fokus, Editiermodus und Tastaturkommandos brauchen gemeinsame Regeln. Sonst entsteht kein Interaktionskern, sondern eine Sammlung zufĂ€lliger Verzweigungen.

Das betrifft auch die Historie. Undo und Redo sind nicht Beiwerk. Sie zwingen dazu, ZustandsÀnderungen explizit zu machen. Ohne diese Disziplin wird jede spÀtere Erweiterung teurer, weil das System intern nicht mehr sauber sagen kann, was sich wann geÀndert hat.

Picking als eigener Pfad

nkrunner behandelt Picking nicht als Nebenprodukt des sichtbaren Bildes. DafĂŒr gibt es einen eigenen Offscreen-Pass mit separatem Farb- und Depth-Attachment. In diesen Pass wird nicht „schön“ gezeichnet, sondern eindeutig. Jedes Objekt bekommt eine Picking-Farbe. Der Mauszeiger liest anschließend genau ein Pixel aus diesem Buffer zurĂŒck. Daraus entsteht die Objekt-ID, und erst danach wird entschieden, ob ein Resize-Handle, ein Fenster oder ein anderes Objekt getroffen wurde.

Das ist der richtige Ort fĂŒr Picking. Sichtbare Geometrie und auswertbare Geometrie sind zwei verschiedene Probleme. Ein dĂŒnnes Element kann optisch korrekt sein und trotzdem schlecht treffbar. Ein eigener Picking-Pfad erlaubt andere Regeln als der sichtbare Renderpfad. Genau deshalb gibt es im Projekt auch zusĂ€tzliche FangflĂ€chen, etwa fĂŒr kleine HUD-Elemente oder Textbereiche. Bedienung hĂ€ngt daran, dass Dinge zuverlĂ€ssig adressierbar bleiben, nicht daran, dass sie nur korrekt aussehen.

Shader und Shader-Compiler

Der Shader-Pfad ist ebenfalls klar getrennt. Ein Hauptshader verarbeitet Position, Normalen und Farbe und berechnet daraus Weltposition, transformierte Normale und Fragmentfarbe. Im Fragmentpfad gibt es mehrere Modi: beleuchtete 3D-Ausgabe, unbeleuchtete Solid-Farben fĂŒr Rahmen und ein reiner Linienmodus. Das ist sauberer als ad hoc verteilte Spezialshader fĂŒr jeden Einzelfall.

FĂŒr 3D-Geometrie wird im Shader nicht nur flach Farbe ausgegeben. Es laufen Ambient-, Diffuse- und Specular-Anteile zusammen. Die Kameraposition geht als Uniform ein, damit Glanzlichter aus der Blickrichtung berechnet werden können. Das ist kein Gimmick, sondern Teil eines konsistenten Material- und Sichtbarkeitsmodells.

Der Shadercode bleibt dabei nicht an ein einzelnes Backend gebunden. Er wird mit sokol-shdc in backend-spezifische Beschreibungen ĂŒbersetzt. Statt denselben Pfad mehrfach per Hand fĂŒr verschiedene Grafik-Backends zu pflegen, gibt es einen zentralen Shader-Eingang und daraus erzeugte Backend-Artefakte.

Wichtig ist dabei die genaue Einordnung:

  • Die Hostsprache des Projekts ist Nim.
  • Die primĂ€re Shader-Quellsprache im Projekt ist GLSL.
  • Genauer gesagt wird eine von sokol-shdc erwartete Form von annotated GLSL bzw. Vulkan-style GLSL verwendet.
  • Metal, HLSL und WGSL werden im Projekt nicht als primĂ€re Shaderquellen von Hand geschrieben.
  • Stattdessen wird ein zentraler GLSL-Shader gepflegt, aus dem die backend-spezifischen Beschreibungen erzeugt werden.
  • Daraus entstehen Bindings, Attribut-Layouts, Uniform-Blöcke und Shader-Deskriptoren fĂŒr GLSL, Metal, HLSL und WGSL.
  • Der eigentliche Vorteil liegt nicht nur in Bequemlichkeit, sondern in Konsistenz: Vertex-Layout, Uniform-Schnittstellen und Shader-Metadaten werden nicht backendweise auseinandergezogen.
  • Dadurch sinkt die Gefahr, dass ein Shader auf einem Backend bereits angepasst wurde, auf einem anderen aber noch mit veralteten Bindings oder Layout-Annahmen lĂ€uft.
  • Die Sprache des Projekts fĂŒr Shader ist also nicht „Nim-Shader“, sondern weiterhin GLSL; Nim steuert nur die Hostseite, also Pipelines, Ressourcen, Draw-Aufrufe und Zustandsverwaltung.
  • sokol-shdc ist in diesem Zusammenhang der Übersetzungs- und Ableitungspfad zwischen der einen gepflegten Shaderquelle und den verschiedenen Ziel-Backends.

Gerade diese Trennung ist architektonisch sauber. Nim beschreibt die Programmlogik auf CPU-Seite. Der Shader bleibt ein Shader. Und die Backend-Anpassung wird nicht als verstreute Handarbeit ĂŒber das Projekt verteilt, sondern in einen klaren Compilerpfad verlagert.

Pipelines statt impliziter ZustÀnde

Ein erheblicher Teil der Architektur steckt in den Pipeline-Beschreibungen. Primitive-Typ, Sample-Count, Depth-Test, Blendzustand, Vertex-Layout und Cull-Mode werden pro Pfad explizit gesetzt. Das ist nicht dekorativ, sondern zwingend. Linien, Dreiecke, Picking, HUD-Quads, transparente Überlagerungen und Fullscreen-Ausgabe haben unterschiedliche Anforderungen. Wer das in lose Zustandsschalter zerlegt, verliert schnell die Kontrolle.

Im Projekt ist das bereits sichtbar. Es gibt getrennte Pipelines fĂŒr Linien, Dreiecke, Picking, Lasso, HUD-Picking, Fullscreen-Quad und Text. Dazu kommen unterschiedliche Sample-Counts und verschiedene Depth- und Blend-Regeln. Eine Linie lĂ€uft anders als ein gefĂŒlltes Objekt. Ein Picking-Pass lĂ€uft anders als die sichtbare Szene. Ein Text-Pass mit Alphablending stellt andere Anforderungen als ein opaker Körper im Raum.

Diese Trennung ist keine akademische Reinheit. Sie reduziert Kopplung. Eine Pipeline beschreibt, unter welchen Bedingungen ein bestimmter Pfad gĂŒltig ist. Dadurch bleibt sichtbar, was eine Renderstufe wirklich erwartet.

Stencil fĂŒr Hover und Selektion

Hover- und Selektionsrahmen werden nicht als lockere Übermalung behandelt. DafĂŒr werden Stencil-ZustĂ€nde benutzt. Zuerst markiert eine Pipeline die FlĂ€che des Objekts im Stencil-Buffer. Danach zeichnet eine zweite Pipeline nur dort, wo der Stencil-Test nicht mehr auf die ursprĂŒngliche FlĂ€che trifft. So entsteht eine Outline, die geometrisch vom Objekt getrennt bleibt.

Das ist die saubere Lösung fĂŒr stabile Rahmen. Eine bloß ĂŒber das Objekt gelegte zweite FlĂ€che kippt schnell in Tiefenprobleme, Überdeckungen oder unscharfe Hervorhebungen. Ein Stencil-basierter Pfad bleibt prĂ€ziser. Im Projekt werden dafĂŒr unterschiedliche Referenzwerte fĂŒr Hover und Selektion verwendet. Damit können beide ZustĂ€nde technisch getrennt behandelt werden, obwohl sie im selben Bildraum liegen.

Text als Belastungstest

Text ist fast immer der Punkt, an dem eine UI-Architektur ihre SchwÀchen offenlegt. nkrunner geht diesen Teil nicht als Bitmap-Text an, sondern mit SDF-Text. Der Font-Atlas wird aus JSON-Metriken und Textur geladen. Der Textshader liest die Distanzwerte aus dem Atlas und berechnet die Kanten im Fragmentshader mit fwidth und smoothstep. Das ergibt skalierbaren Text ohne sofortige Treppenkanten.

Wichtiger als die Technik des Font-Atlas ist aber, was daran hĂ€ngt: Cursorgeometrie, Blinklogik, Navigation, EinfĂŒgen, Löschen, Scrollen und sichtbare RĂŒckmeldung. An Text wird schnell klar, ob Eingaben, Layout und Darstellung wirklich zusammenspielen. Ein Projekt, das Text nur anzeigt, ist an dieser Stelle noch harmlos. Ein Projekt, das Text editiert, muss intern deutlich sauberer werden.

NebenlÀufigkeit

Auch Threading taucht im Projekt nicht nur als Theorie auf. Es gibt einen klassischen Analyse-Thread mit Lock-Schutz fĂŒr gemeinsame Meldungsdaten, und daneben einen separaten Worker mit atomarem Integer-Zustand. Das ist noch kein großer Scheduler und kein ausgebautes Jobsystem. Es zeigt aber die richtige Trennlinie: Hintergrundarbeit darf den UI-Takt nicht blockieren.

Das ist fĂŒr Werkzeuge wichtiger als fĂŒr bloße Viewer. Sobald Dateianalyse, Auswertung oder spĂ€tere Berechnungen neben Eingaben laufen, muss der Hauptthread frei bleiben. Sonst beginnt die OberflĂ€che zu stottern, obwohl die Algorithmen fĂŒr sich genommen korrekt sind. ReaktionsfĂ€higkeit hĂ€ngt daher nicht nur an Zeichenroutinen, sondern auch an der Art, wie ZustĂ€nde zwischen Threads ĂŒbergeben werden.

Einordnung

Figma arbeitet auf der Ebene des Entwurfs. Dort geht es um Komponenten, Layout, Varianten und Übergabe. Das ist ein anderes Problemfeld. nkrunner liegt tiefer. Hier geht es nicht um die Beschreibung einer OberflĂ€che, sondern um deren AusfĂŒhrung als System: ObjektidentitĂ€t, Picking, Text, Pipeline-ZustĂ€nde, Shader, Historie und Eingabefluss.

Auch im Nim-Umfeld ist das keine triviale Nische. Deklarative OberflĂ€che, Web-Denken oder Design-ÜberfĂŒhrung sind eigene Richtungen. nkrunner zielt auf den technischen Kern eines laufenden Werkzeugs. Dieser Kern ist langsamer aufzubauen als ein Satz sichtbarer Screens. Er ist dafĂŒr architektonisch ergiebiger.

Schluss

nkrunner ist damit keine Sammlung netter Einzeltricks. Der Wert liegt in der Kopplung der richtigen Probleme: Picking nicht im sichtbaren Bild, sondern im eigenen Pass. Hover und Selektion nicht als Farbklecks, sondern ĂŒber Stencil-ZustĂ€nde. Text nicht als Sprite-Haufen, sondern ĂŒber SDF und echte Eingabelogik. Shader nicht backendweise von Hand, sondern ĂŒber einen Compilerpfad. Hintergrundarbeit nicht im UI-Takt, sondern getrennt.

Wer diese Probleme einzeln löst, bekommt einzelne Features. Wer sie in eine gemeinsame Struktur zwingt, baut eine Grundlage. Genau dort wird entschieden, ob aus einer OberflÀche ein Werkzeug wird.