4 puntos por GN⁺ 2024-05-26 | 1 comentarios | Compartir por WhatsApp
  • Spot es un toolkit reactivo simple para crear GUI de escritorio multiplataforma en Go; usa widgets nativos cuando es posible y ofrece de forma consistente APIs específicas de cada plataforma
  • Funciona recreando un árbol de componentes inmutable cuando cambia el estado de la aplicación, y comparándolo con el estado anterior para decidir qué controles de UI actualizar
  • Los backends actuales son una implementación basada en Cocoa para macOS y otra basada en FLTK para las demás plataformas; en macOS también se puede usar FLTK opcionalmente
  • spot es el paquete core independiente del backend que proporciona el modelo reactivo y el renderizado, y spot/ui es una colección de controles GUI multiplataforma ya preparados
  • Todavía no ofrece auto layout, múltiples ventanas, diálogos modales, ventanas redimensionables, barra de menús, widgets personalizados, acceso a widgets nativos, drag and drop ni internacionalización

Objetivo y modelo básico de Spot

  • Spot es un toolkit de GUI reactivo para Go, diseñado para usar widgets nativos donde sea posible y ofrecer una API consistente en varias plataformas
  • Al agregarlo como una dependencia simple al proyecto y escribir solo código Go, se pueden crear binarios GUI nativos autocontenidos sin herramientas adicionales ni generación de código
  • El ejemplo crea una ventana y un botón con el flujo ui.Init(), spot.MountFn(...), ui.Run(), y administra el estado del conteo de clics con spot.UseState[int](<https://github.com/roblillack/ctx, 0>)
  • El handler del clic del botón llama a setCounter(counter + 1) y, cuando cambia el estado, el título del botón cambia con el formato "Clicked %d times!"

Método de actualización reactiva

  • En Spot, reactive significa que la UI se actualiza automáticamente cuando cambia el estado de la aplicación
  • Ante un cambio de estado, recrea el árbol de componentes inmutable y lo compara rápidamente con el estado anterior para decidir qué controles de UI actualizar
  • En la web, esta idea suele llamarse virtual DOM, y Spot nació de un experimento para llevar este concepto al entorno de escritorio de Go e implementar una biblioteca GUI similar a React
  • En lugar de actualizar la UI manualmente, el desarrollador administra la lógica y el estado de la aplicación mediante funciones de renderizado sin efectos secundarios y hooks como UseState

Backends y organización de paquetes

  • Spot selecciona automáticamente en tiempo de compilación el backend adecuado para la plataforma de ejecución
  • Actualmente se ofrecen dos backends
  • En macOS usa el backend Cocoa, y en las demás plataformas usa el backend basado en FLTK
  • También se puede usar FLTK opcionalmente en macOS, y la mejora del soporte para Windows queda como plan futuro
  • spot es el paquete core que proporciona el modelo reactivo y las funciones de renderizado, y puede usarse con cualquier conjunto de controles que implemente la interfaz spot.Control
  • spot/ui es un paquete de controles GUI multiplataforma ya preparados para usar con Spot

Componentes, controles y hooks

  • Como en React, se pueden crear hooks personalizados
    • Se crea una función que recibe *spot.RenderContext como primer argumento y se llama a spot.UseState, spot.UseEffect, etc. para conectarse con el ciclo de vida de Spot
    • Por convención, el nombre de la función lleva el prefijo Use…
  • Los componentes personalizados pueden crearse como structs que implementan la interfaz spot.Component
    • Esta interfaz tiene un único método: Render(ctx *spot.RenderContext) spot.Component
    • Los componentes creados de esta forma pueden usarse igual que los componentes integrados
  • En Spot, un component es una unidad lógica que contiene lógica de negocio y estado
    • Los componentes se componen de otros componentes y finalmente se renderizan como uno o más controles
  • Un control es un componente especial que se monta en el árbol de UI y representa un elemento visual en pantalla
    • Por lo general se basa en una implementación nativa del backend GUI, como un botón, una etiqueta o una entrada de texto
  • También puede usarse una biblioteca de widgets completamente distinta de la biblioteca de widgets incluida
    • Basta con crear structs que implementen la interfaz spot.Component y administren widgets nativos
  • Actualmente no se admite usar spot/ui con backends que no sean Cocoa o FLTK

Términos del ciclo de vida de renderizado

  • Make: proceso de crear una instancia de un struct que implementa la interfaz spot.Component, o de llamar a spot.Make con una función de renderizado para crear una nueva instancia de componente
  • Render: proceso de aplicar el estado del componente a sus elementos y devolver otra instancia de componente
  • Build: proceso de renderizar componentes recursivamente para crear un árbol de controles
    • Se ejecuta pasando una instancia de componente a spot.Build, o pasando una función de renderizado a spot.BuildFn
  • Mount: proceso de crear controles de UI reales a partir del árbol de controles virtual
    • Puede hacerse llamando a Mount en el nodo del árbol, o usando spot.Mount, spot.MountFn
  • Update: proceso de actualizar el árbol de controles montado
    • Se realiza llamando a Update en el nodo del árbol

Funciones que no ofrece actualmente

  • Actualmente Spot no ofrece las siguientes funciones
    • Auto layout

      • Múltiples ventanas
      • Diálogos modales
      • Ventanas redimensionables
      • Barra de menús
      • Widgets personalizados
      • Acceso a widgets nativos
      • Drag and drop
      • Internacionalización

Controles de UI soportados

  • Spot incluye de forma predeterminada varios controles de UI, como botones, etiquetas, entradas de texto, sliders y dropdowns
  • Los estados de soporte se indican como ❓ no implementado, 🚧 en progreso, ⚠️ implementación parcial y ✅ completado
  • Los principales controles completados son los siguientes
    • Button: botón para ejecutar una acción simple; usa Fl_Button y NSButton
    • Checkbox: control para elegir entre dos opciones; usa Fl_Check_Button y NSButton
    • Dropdown: dropdown para elegir una opción entre varios elementos; usa Fl_Choice y NSComboBox
    • Image: control para mostrar imágenes bitmap; usa Fl_Box y un NSButton personalizado
    • Label: etiqueta de texto no editable; usa Fl_Box y NSTextField
    • ListBox: control de lista con selección única o múltiple; usa Fl_Select_Browser/Fl_Multi_Browser y NSTableView
    • ProgressBar: muestra el progreso de tareas largas; usa Fl_Progress y NSProgressIndicator
    • Slider: entrada con slider horizontal; usa Fl_Slider y NSSlider
    • Spinner: entrada numérica con botones arriba/abajo; usa Fl_Spinner y NSTextField+NSStepper
    • TextField: entrada de texto de una sola línea; usa Fl_Input y NSTextField
    • TextEditor: edición de texto multilínea; usa Fl_Text_Editor y NSTextView
    • Window: control de ventana de nivel superior; usa Fl_Window y NSWindow
  • También hay controles parcialmente implementados o no implementados
    • Dial: control circular de estado, con estado ⚠️ de implementación parcial
    • ComboBox: menú dropdown combinado con entrada de texto, aún no iniciado
  • Se propone https://github.com/rodrigocfd/windigo como posible candidato de backend futuro para una biblioteca de controles nativos de Windows

1 comentarios

 
GN⁺ 2024-05-26
Opiniones en Hacker News
  • Tengo que ver esto sí o sí. Estaba buscando una forma simple de crear herramientas internas de desarrollo en Go; básicamente son formularios con botones y campos de texto.
    También probé Gio, pero me costó entenderlo, y ahora uso wails, que me gusta mucho más. Este proyecto también se ve interesante y parece que vale la pena revisarlo.

  • Recomiendo seriamente reducir el alcance de esa dirección de “multiplataforma: aprovechando FLTK[1] y Cocoa[2], Spot funciona en Mac, Linux y BSD, con planes de soporte nativo para Windows en el futuro”.
    Conviene conservar lo aprendido para mantener flexibilidad más adelante, pero primero es mejor hacerlo bien con un solo toolkit. Los toolkits de GUI, los bindings de GUI y las GUI en sí ya tienden a ahogarse en detalles; si además uno se carga voluntariamente los detalles de varios toolkits base, es muy probable que termine sin hacer bien ninguno.
    Existe el dicho de que “el primer 90% del trabajo toma el 90% del tiempo, y el 10% restante toma otro 90%”, pero en GUI incluso eso suena optimista. El primer 10% es el 90% del trabajo; el siguiente 10% cuesta 10 veces más, y el siguiente 10% otras 10 veces más. Intentar ser multiplataforma puede terminar asfixiándote.
    No espero que estés de acuerdo con esta idea ahora mismo, pero cuando más adelante te encuentres con que tres toolkits te obligan a manejar algo como rich text de tres maneras contradictorias, espero que te des permiso de quedarte solo con el toolkit base mejor soportado o más popular y descartar los demás.

  • Llevo un tiempo buscando algo así en Go. Creo que Go tiene una gran oportunidad de ofrecer una excelente experiencia de desarrollo para UI multiplataforma porque su proceso de build es simple.
    Según mi experiencia, la mitad del dolor del desarrollo multiplataforma está en gestionar la complejidad del build, y Go prácticamente elimina eso.
    Eso sí, me da curiosidad cómo resolverán el layout multiplataforma, ya que el tamaño predeterminado de los controles nativos varía entre plataformas. No he visto que otros toolkits multiplataforma lo resuelvan particularmente bien. Aun así, les deseo suerte.

  • Hace unos años estaba buscando algo así. Pero también necesitaba soporte para Windows. Al final me pasé a C++ para usar wxWidgets, y pude obtener un binario pequeño y autocontenido.

    • go-fltk también compila y corre en Windows, y de hecho funciona bastante bien.
      Me impresionó ver que, siendo un toolkit nativo, FLTK soporta zoom de toda la app con Ctrl-+ y Ctrl+-, como un navegador. Y gracias a https://github.com/fltk-rs/fltk-theme?tab=readme-ov-file#wid..., mejoró mi impresión sobre qué tan “nativo” se puede hacer ver FLTK.
      Relacionado con esto, recientemente también descubrí GoVCL https://z-kit.cc/en/ y quiero probarlo.
    • Me gusta mucho WxWindows, pero hoy en día estoy demasiado metido en Go.
      Un “Hello World” autocontenido de Spot pesa 2.3MiB en mi Mac. No es bonito, pero para mí funciona lo suficiente.
    • Existe wxGo, pero lamentablemente el proyecto ya no recibe mantenimiento.
  • Me pregunto cuál es la ventaja del enfoque de árbol virtual de controles frente a actualizar directamente los controles que se muestran al usuario.

    • En situaciones complejas —por ejemplo, cuando el usuario interactúa con la UI al mismo tiempo que una tarea larga en segundo plano también modifica el estado de la UI—, la gestión de estado se vuelve difícil de manejar muy rápido.
      Terminas escribiendo callbacks por todos lados, y cada callback puede tener que revisar cuidadosamente el estado actual de todas las demás actividades antes de actualizar decenas de widgets.
      Con un enfoque reactivo, escribes una sola función de render que describe la interfaz para un estado dado, y el framework se encarga de cuándo llamarla y qué entradas darle. Es mucho más fácil de entender, y después de usar React cuesta volver atrás, así que me puse a experimentar si algo parecido era posible en Go.
  • Se ve bien. Me pregunto si podrían agregar al README las plataformas soportadas.
    Información como Windows, Linux, macOS, *BSD, Android, iOS, Web o Tizen sería bastante interesante.
    Podrían presentarlo como en la documentación de Flutter: https://docs.flutter.dev/reference/supported-platforms

  • Aplaudo el esfuerzo, pero ¿multiplataforma sin soporte para Windows?

    • Parece que en Windows usa FLTK; simplemente todavía no tendría soporte nativo.
    • Usa WSL y listo.
  • Ojalá hubiera sabido de esto hace 3 semanas, o que hubiera existido entonces según el historial de commits. Desde hace mucho digo que un React portado a Go o un framework similar a React para Go daría una experiencia de desarrollo increíble, y esto parece encajar perfecto.
    Odiaba bastante React.js hasta que React.lua me hizo cambiar de opinión.

    • Tuve el mismo problema. Me encantaba la forma de composición de componentes que usaba en React, y era difícil volver atrás.
      Al final encontré una forma de hacer algo parecido usando solo html/template de la biblioteca estándar de Go, y escribí los detalles de la implementación aquí: https://www.sheshbabu.com/posts/react-like-composition-using...
    • Me da curiosidad qué usaste en su lugar hace 3 semanas.
  • El gran problema es que, cuando lanzas algo para escritorio, normalmente también quieres una versión web. Sobre todo si no es una app muy de nicho que interactúe mucho con el sistema operativo.
    O necesitas algo que apunte a varias plataformas, incluyendo móvil.
    Busqué durante bastante tiempo, y lo más cercano que encontré fue Qt y React Native; ambos eran opciones dolorosas por varias razones.

  • FLTK soporta Windows. Me pregunto si todavía no soporta Windows porque planean usar otra solución.

    • Si el entorno está preparado, con un compilador de C adecuado y demás, Spot debería funcionar en Windows sin cambios y elegir el backend FLTK.
      Sin embargo, mi objetivo —aunque de baja prioridad— es implementar un backend basado en Win32, y el primer paso ya está terminado: https://github.com/roblillack/spot/pull/4