4 Punkte von GN⁺ 2024-05-26 | 1 Kommentare | Auf WhatsApp teilen
  • Spot ist ein einfaches reaktives Toolkit zum Erstellen von plattformübergreifenden Desktop-GUIs in Go, das nach Möglichkeit native Widgets verwendet und plattformspezifische APIs konsistent bereitstellt
  • Es arbeitet, indem bei Änderungen des Anwendungszustands der unveränderliche Komponentenbaum neu erstellt und mit dem vorherigen Zustand verglichen wird, um zu bestimmen, welche UI-Controls aktualisiert werden müssen
  • Die aktuellen Backends sind eine auf Cocoa basierende Implementierung für macOS und eine auf FLTK basierende Implementierung für andere Plattformen; auf macOS kann optional ebenfalls FLTK verwendet werden
  • spot ist das backendunabhängige Core-Paket, das das reaktive Modell und das Rendering bereitstellt, und spot/ui ist eine Sammlung vorgefertigter plattformübergreifender GUI-Controls
  • Automatisches Layout, mehrere Fenster, modale Dialoge, größenveränderbare Fenster, Menüleiste, benutzerdefinierte Widgets, Zugriff auf native Widgets, Drag-and-Drop und Internationalisierung werden noch nicht bereitgestellt

Zweck und Grundmodell von Spot

  • Spot ist ein reaktives GUI-Toolkit für Go, das so entwickelt wurde, dass es nach Möglichkeit native Widgets verwendet und gleichzeitig über mehrere Plattformen hinweg eine konsistente API bietet
  • Es kann dem Projekt als einfache Abhängigkeit hinzugefügt werden; wenn man nur Go-Code schreibt, lassen sich ohne zusätzliche Tools oder Codegenerierung eigenständige native GUI-Binärdateien erstellen
  • Das Beispiel erstellt Fenster und Button im Ablauf ui.Init(), spot.MountFn(...), ui.Run() und verwaltet den Zustand der Klickanzahl mit spot.UseState[int](<https://github.com/roblillack/ctx, 0>)
  • Der Klick-Handler des Buttons ruft setCounter(counter + 1) auf, und wenn sich der Zustand ändert, wird der Titel des Buttons in das Format "Clicked %d times!" geändert

Reaktiver Update-Ansatz

  • Reactive bedeutet in Spot, dass die UI automatisch aktualisiert wird, wenn sich der Anwendungszustand ändert
  • Bei einer Zustandsänderung wird der unveränderliche Komponentenbaum neu erstellt und schnell mit dem vorherigen Zustand verglichen, um zu entscheiden, welche UI-Controls aktualisiert werden sollen
  • Im Web wird diese Idee oft als virtual DOM bezeichnet; Spot brachte dieses Konzept in die Go-Desktop-Umgebung und begann als Experiment, eine React-ähnliche GUI-Bibliothek zu implementieren
  • Statt die UI manuell zu aktualisieren, verwalten Entwickler die Anwendungslogik und den Zustand mit nebenwirkungsfreien Rendering-Funktionen und Hooks wie UseState

Backends und Paketstruktur

  • Spot wählt beim Kompilieren automatisch das zum Zielsystem passende Backend aus
  • Derzeit werden zwei Backends bereitgestellt
  • Auf macOS wird das Cocoa-Backend verwendet, auf anderen Plattformen das FLTK-basierte Backend
  • Auf macOS kann optional auch FLTK verwendet werden; eine Verbesserung der Windows-Unterstützung ist für die Zukunft geplant
  • spot ist das Core-Paket, das das reaktive Modell und Rendering-Funktionen bereitstellt, und kann mit jeder Control-Sammlung verwendet werden, die das Interface spot.Control implementiert
  • spot/ui ist ein Paket mit vorgefertigten plattformübergreifenden GUI-Controls, das zusammen mit Spot verwendet werden kann

Komponenten, Controls und Hooks

  • Wie in React können benutzerdefinierte Hooks erstellt werden
    • Man erstellt eine Funktion, die als erstes Argument *spot.RenderContext erhält, und ruft spot.UseState, spot.UseEffect usw. auf, um sie in den Spot-Lebenszyklus einzubinden
    • Konventionell beginnt der Funktionsname mit dem Präfix Use…
  • Benutzerdefinierte Komponenten können als Structs erstellt werden, die das Interface spot.Component implementieren
    • Dieses Interface hat die einzelne Methode Render(ctx *spot.RenderContext) spot.Component
    • So erstellte Komponenten können auf dieselbe Weise wie eingebaute Komponenten verwendet werden
  • Eine component ist in Spot die logische Einheit, die Business-Logik und Zustand enthält
    • Komponenten setzen sich aus anderen Komponenten zusammen und werden letztlich in ein oder mehrere Controls gerendert
  • Ein control ist eine spezielle Komponente, die in den UI-Baum gemountet wird und ein visuelles Element auf dem Bildschirm darstellt
    • In der Regel basiert es auf nativen Implementierungen des GUI-Backends wie Buttons, Labels oder Texteingaben
  • Es können auch vollständig andere Widget-Bibliotheken als die bereitgestellte Widget-Bibliothek verwendet werden
    • Dazu erstellt man ein Struct, das das Interface spot.Component implementiert und native Widgets verwaltet
  • Die Verwendung von spot/ui mit anderen Backends als Cocoa oder FLTK wird derzeit nicht unterstützt

Begriffe des Rendering-Lebenszyklus

  • Make: der Vorgang, eine Struct-Instanz zu erstellen, die das Interface spot.Component implementiert, oder mit spot.Make zusammen mit einer Render-Funktion eine neue Komponenteninstanz zu erzeugen
  • Render: der Vorgang, den Komponentenstatus auf Unterelemente anzuwenden und andere Komponenteninstanzen zurückzugeben
  • Build: der Vorgang, durch rekursives Rendern von Komponenten einen Control-Baum zu erzeugen
    • Dies geschieht, indem man eine Komponenteninstanz an spot.Build übergibt oder eine Render-Funktion an spot.BuildFn
  • Mount: der Vorgang, aus dem virtuellen Control-Baum reale UI-Controls zu erzeugen
    • Dies kann durch Aufruf von Mount auf einem Baumknoten oder mit spot.Mount bzw. spot.MountFn erfolgen
  • Update: der Vorgang, einen gemounteten Control-Baum zu aktualisieren
    • Dies geschieht durch Aufruf von Update auf einem Baumknoten

Derzeit nicht bereitgestellte Funktionen

  • Spot bietet derzeit die folgenden Funktionen nicht
    • Automatisches Layout

      • mehrere Fenster
      • modale Dialoge
      • größenveränderbare Fenster
      • Menüleiste
      • benutzerdefinierte Widgets
      • Zugriff auf native Widgets
      • Drag-and-Drop
      • Internationalisierung

Unterstützte UI-Controls

  • Spot stellt standardmäßig verschiedene UI-Controls wie Buttons, Labels, Texteingaben, Slider und Dropdowns bereit
  • Die Kennzeichnung des Unterstützungsstatus ist in ❓ nicht implementiert, 🚧 in Arbeit, ⚠️ teilweise implementiert und ✅ abgeschlossen unterteilt
  • Die wichtigsten vollständig unterstützten Controls sind folgende
    • Button: Button zum Ausführen einfacher Aktionen, verwendet Fl_Button und NSButton
    • Checkbox: Control zur Auswahl einer von zwei Optionen, verwendet Fl_Check_Button und NSButton
    • Dropdown: Dropdown zur Auswahl eines Eintrags aus mehreren Optionen, verwendet Fl_Choice und NSComboBox
    • Image: Control zur Anzeige von Bitmap-Bildern, verwendet Fl_Box und ein benutzerdefiniertes NSButton
    • Label: nicht editierbares Textlabel, verwendet Fl_Box und NSTextField
    • ListBox: Listen-Control für Einzel- oder Mehrfachauswahl, verwendet Fl_Select_Browser/Fl_Multi_Browser und NSTableView
    • ProgressBar: Anzeige des Fortschritts länger laufender Aufgaben, verwendet Fl_Progress und NSProgressIndicator
    • Slider: horizontale Slider-Eingabe, verwendet Fl_Slider und NSSlider
    • Spinner: numerische Eingabe mit Auf-/Ab-Buttons, verwendet Fl_Spinner und NSTextField+NSStepper
    • TextField: einzeilige Texteingabe, verwendet Fl_Input und NSTextField
    • TextEditor: mehrzeilige Textbearbeitung, verwendet Fl_Text_Editor und NSTextView
    • Window: Control für das oberste Fenster, verwendet Fl_Window und NSWindow
  • Es gibt auch teilweise implementierte oder noch nicht implementierte Controls
    • Dial: kreisförmiges Status-Control mit ⚠️ teilweise implementiertem Status
    • ComboBox: Dropdown-Menü mit kombinierter Texteingabe, wurde noch nicht begonnen
  • Als potenzieller künftiger Backend-Kandidat wird die native Windows-Control-Bibliothek https://github.com/rodrigocfd/windigo genannt

1 Kommentare

 
GN⁺ 2024-05-26
Meinungen auf Hacker News
  • Das muss ich mir unbedingt ansehen. Ich war auf der Suche nach einer einfachen Möglichkeit, interne Entwicklungstools in Go zu bauen; im Grunde geht es um Formulare mit Buttons und Textfeldern.
    Gio habe ich auch ausprobiert, fand es aber schwer zu verstehen; aktuell nutze ich wails, was mir deutlich besser gefällt. Dieses Projekt wirkt ebenfalls interessant und einen Blick wert.

  • Ich würde ernsthaft empfehlen, die Richtung „Cross-Plattform: Durch die Nutzung von FLTK[1] und Cocoa[2] läuft Spot auf Mac, Linux und BSD; native Windows-Unterstützung ist künftig geplant“ deutlich zu reduzieren.
    Behaltet die Lernerfahrungen bei, um später flexibel zu bleiben, aber konzentriert euch zuerst darauf, in einem Toolkit richtig gut zu sein. GUI-Toolkits, GUI-Bindings und GUIs selbst sind ohnehin anfällig dafür, in Details zu ertrinken; wenn man sich freiwillig auch noch die Details mehrerer zugrunde liegender Toolkits auflädt, ist die Wahrscheinlichkeit groß, dass man am Ende keines davon wirklich gut unterstützt.
    Es gibt den Spruch: „Die ersten 90 % machen 90 % der Arbeit aus, und die verbleibenden 10 % noch einmal 90 %.“ Bei GUIs wirkt selbst das noch optimistisch. Die ersten 10 % sind 90 % der Arbeit, die nächsten 10 % das Zehnfache davon, und die nächsten 10 % wieder das Zehnfache. Cross-Plattform kann einem schnell die Luft abschnüren.
    Ich erwarte nicht, dass ihr dem sofort zustimmt, aber wenn ihr später auf Situationen stoßt, in denen drei Toolkits Dinge wie Rich-Text-Verarbeitung auf drei einander widersprechende Arten erzwingen, dann erlaubt euch selbst, alle bis auf das am besten unterstützte oder beliebteste zugrunde liegende Toolkit fallenzulassen.

  • Nach so etwas habe ich in Go schon eine Weile gesucht. Weil Go einen einfachen Build-Prozess hat, sehe ich hier eine große Chance für eine hervorragende Developer Experience bei Cross-Plattform-UIs.
    Meiner Erfahrung nach besteht die Hälfte des Schmerzes bei Cross-Plattform-Entwicklung aus dem Management von Build-Komplexität, und Go nimmt einem das fast vollständig ab.
    Allerdings frage ich mich, wie ihr Cross-Plattform-Layouts lösen wollt, da die Standardgrößen nativer Controls je nach Plattform unterschiedlich sind. Ich habe das auch bei anderen Cross-Plattform-Toolkits noch nicht wirklich gut gelöst gesehen. Trotzdem viel Erfolg.

  • Vor ein paar Jahren habe ich nach genau so etwas gesucht. Allerdings brauchte ich auch Windows-Unterstützung. Am Ende bin ich auf C++ umgestiegen, um wxWidgets zu verwenden, und bekam damit kleine, in sich geschlossene Binaries.

    • go-fltk lässt sich auch unter Windows bauen und ausführen und funktioniert tatsächlich ziemlich gut.
      Für ein natives Toolkit fand ich es beeindruckend, dass FLTK wie ein Browser mit Strg-+ und Strg+- Zoom für die gesamte App unterstützt. Und https://github.com/fltk-rs/fltk-theme?tab=readme-ov-file#wid... hat meinen Eindruck verbessert, wie „nativ“ man FLTK aussehen lassen kann.
      In dem Zusammenhang habe ich kürzlich auch GoVCL https://z-kit.cc/en/ entdeckt und möchte es einmal ausprobieren.
    • Ich mag WxWindows sehr, aber heutzutage stecke ich zu tief in Go.
      Ein in sich geschlossenes Spot-„Hello World“ ist auf meinem Mac 2,3 MiB groß. Es ist nicht hübsch, aber für mich funktioniert es gut genug.
    • Es gibt zwar wxGo, aber leider wird das Projekt nicht mehr gepflegt.
  • Ich frage mich, welchen Vorteil der Ansatz mit einem virtuellen Control-Tree gegenüber dem direkten Aktualisieren der dem Nutzer angezeigten Controls hat.

    • In komplexen Situationen, etwa wenn der Nutzer mit der UI interagiert, während gleichzeitig ein lang laufender Hintergrundtask ebenfalls den UI-Zustand verändert, wird State Management schnell unbeherrschbar.
      Man muss überall Callback-Code schreiben, und jeder Callback muss möglicherweise vorsichtig den aktuellen Zustand aller anderen Aktivitäten prüfen und dann Dutzende Widgets aktualisieren.
      Beim reaktiven Ansatz schreibt man eine einzelne Render-Funktion, die das Interface für einen gegebenen Zustand beschreibt; das Framework kümmert sich darum, wann sie aufgerufen wird und welche Eingaben sie bekommt. Das ist deutlich leichter zu verstehen, und nachdem man React verwendet hat, ist es schwer, zurückzugehen – deshalb habe ich experimentiert, ob etwas Ähnliches auch in Go möglich ist.
  • Sieht gut aus. Ich frage mich, ob ihr im README die unterstützten Plattformen aufführen könntet.
    Informationen wie Windows, Linux, macOS, *BSD, Android, iOS, Web, Tizen wären ziemlich interessant.
    Man könnte es auch ähnlich wie in der Flutter-Dokumentation machen: https://docs.flutter.dev/reference/supported-platforms

  • Der Aufwand ist lobenswert, aber Cross-Plattform ohne Windows-Unterstützung?

    • Unter Windows scheint FLTK verwendet zu werden; es fehlt offenbar nur noch native Unterstützung.
    • Man kann WSL verwenden.
  • Wenn ich das doch vor drei Wochen gekannt hätte – oder wenn es laut Commit-Historie damals schon existiert hätte. Ich sage schon lange, dass ein nach Go portiertes React oder ein React-ähnliches Framework für Go eine großartige Developer Experience bieten würde, und das hier sieht genau passend aus.
    Ich mochte React.js ziemlich wenig, bis ich durch React.lua verstanden habe, worum es geht.

    • Ich hatte dasselbe Problem. Die Art der Komponenten-Komposition, wie man sie in React macht, gefiel mir wirklich gut, und es war schwer, davon wieder wegzugehen.
      Am Ende habe ich einen Weg gefunden, mit dem Go-Standardpaket html/template etwas Ähnliches zu machen, und die Umsetzung hier beschrieben: https://www.sheshbabu.com/posts/react-like-composition-using...
    • Mich würde interessieren, was du vor drei Wochen stattdessen verwendet hast.
  • Das große Problem ist: Wenn man etwas für den Desktop veröffentlicht, will man in der Regel auch eine Web-Version. Besonders dann, wenn es keine sehr nischige App ist, die stark mit dem Betriebssystem interagiert.
    Oder man braucht etwas, das mehrere Plattformen einschließlich Mobile abdeckt.
    Ich habe ziemlich lange gesucht; am nächsten kamen Qt und React Native, aber beide waren aus verschiedenen Gründen schmerzhafte Optionen.

  • FLTK unterstützt Windows. Ich frage mich, ob Windows noch nicht unterstützt wird, weil ein anderer Lösungsansatz geplant ist.

    • Wenn die Umgebung einschließlich eines passenden C-Compilers eingerichtet ist, sollte Spot unter Windows unverändert laufen und das FLTK-Backend auswählen.
      Mein Ziel ist allerdings, wenn auch mit niedriger Priorität, ein Win32-basiertes Backend zu implementieren; der erste Schritt ist erledigt: https://github.com/roblillack/spot/pull/4