Show HN: Spot – Simple, cross-platform, reactive desktop GUI toolkit for Go

2024-05-2419:1930957github.com

React-like desktop GUI toolkit for Go. Contribute to roblillack/spot development by creating an account on GitHub.

Go Reference Go Report Card

Spot is a simple, cross-platform, reactive GUI toolkit for Go using native widgets where available. It is designed to be easy to use and to provide a consistent API across different platforms.

package main import ( "fmt" "github.com/roblillack/spot" "github.com/roblillack/spot/ui"
) func main() { ui.Init() spot.MountFn(func(ctx *spot.RenderContext) spot.Component { counter, setCounter := spot.UseState[int](ctx, 0) buttonTitle := "Click me!" if counter > 0 { buttonTitle = fmt.Sprintf("Clicked %d times!", counter) } return &ui.Window{ Title: "Hello World!", Width: 200, Height: 125, Children: []spot.Component{ &ui.Button{ X: 25, Y: 50, Width: 150, Height: 25, Title: buttonTitle, OnClick: func() { setCounter(counter + 1) }, }, }, } }) ui.Run()
}
  • Simple: You can add Spot as a simple dependency to your project and start building your UI right away. No need to use additional tools or code generation steps. Just write Go code and get a native GUI application as a self-contained binary.
  • Cross-platform: Spot uses native widgets where available and automatically selects the best backend for the platform you are running on at compile time. Currently, two backend implementations are provided: one based on FLTK using go-fltk and one based on Cocoa using (a modified version of) gocoa.
  • Reactive: Spot automatically updates the UI when the state of the application changes. You just provide side-effect free rendering functions and manage the state of your application using the UseState hook.
  • Broad widget support: Spot provides a wide range of UI controls out of the box, including buttons, labels, text inputs, sliders, dropdowns, and more. See the full list: List of supported UI controls.

In the context of Spot, reactive means that the UI is automatically updated when the state of the application changes. This is achieved by re-building an immutable component tree upon state changes which can quickly be compared to the previous state in order to determine what UI controls need to be updated. In the web world, this idea is often called a "virtual DOM" and Spot actually started as an experiment to bring this concept to Go by implementing a React-like GUI library for the desktop.

By using a reactive model, the developer does not need to worry about updating the UI manually. Instead, the developer can focus on the application logic and let Spot take care of updating the UI.

Currently, Spot uses a Cocoa backend on macOS and a FLTK-based one on all other platforms. Optionally, FLTK can be used on the Mac, too. Better support for Windows is planned for the future.

Yes, just like in React, you can implement your own hooks. Just create a function which takes a *spot.RenderContext as first argument and use this to "hook" into the Spot lifecycle by calling spot.UseState, spot.UseEffect, etc. Convention here is to prefix the function with Use….

There are a few different ways to separate your UI into components in Spot; for some ideas, check out the custom-components example. The main way to write custom components is to create a struct that implements the spot.Component interface. This interface has a single method, Render(ctx *spot.RenderContext) spot.Component, which is called to render the component. Components created like this can be used in the same way as the built-in ones.

Look at the BlinkingButton component in the example to see how this is done.

Yes, you can. You just need to create some structs that implement the spot.Component interface and which take care of managing the native widgets.

Currently, these are the only backends that are supported. But feel free to create a PR if you want to add support for another backend. *hint hint*

spot is the core package that provides the reactive model and the rendering functionality. It is backend-agnostic and can be used with any set of controls which implement the spot.Control interface.

spot/ui is a package that provides a set of pre-built cross-platform GUI controls that which can be used with spot.

In Spot, a component is a logical unit of the application that contains business logic and state. Any component is made out of other componens and can ultimately be rendered down to a single or multiple "controls".

A control is special kind component is mounted to the UI tree and represents a visual element on the screen. Usually a control is backed by a native implementation of the GUI backend, like a button, a label, or a text input.

  • Make: The process of creating a new component instance. This is done by creating a reference to an instance of a struct that implements the spot.Component interface or by calling spot.Make with a render function.

  • Render: The process of applying a component's state to its building blocks and hereby returning another component instance. This is done by calling the Render method on a component instance.

  • Build: The process of creating a new UI tree from a component instance. This is done by recursively rendering a component to create a tree of controls. This can be done by calling spot.Build with a component instance or spot.BuildFn with a render function.

  • Mount: The process of creating real UI controls from a (virtual) tree of controls. This is done by calling Mount on a tree node or spot.Mount with a component instance or spot.MountFn with a render function.

  • Update: The process of updating a tree of (mounted) controls. This is done by calling Update on a tree node.

  • Automatic layouting
  • Multiple windows
  • Modal dialogs
  • Resizable windows
  • Menu bars
  • Custom widgets
  • Access to native widgets
  • Drag and drop
  • Internationalization

Explanation of the status column:
❓ Not implemented / 🚧 Work in progress / ⚠️ Partially implemented / ✅ Done


Read the original article

Comments

  • By heywire 2024-05-250:181 reply

    I’ll have to give this a look! I’ve been looking for a simple way to use Go to write an internal development tool which is basically just a form with some buttons and text fields. I tried Gio, but had a hard time with wrapping my head around it. Right now I’m using wails and like it much better. This looks interesting and worth a look!

    • By da_rob 2024-05-255:14

      Perfect use-case, IMHO! Looking forward to hear your thoughts ocne you tried it.

  • By jerf 2024-05-272:09

    "Cross-platform: Leveraging FLTK[1] & Cocoa[2], Spot works on Mac, Linux, and the BSDs with plans for native Windows support in the future."

    I'd seriously recommend that you consider cutting this. Perhaps keep the lessons you've learned to retain future flexibility, but get good on one toolkit first. GUI toolkits, GUI bindings, GUI in general drowns you in details as it is, volunteering to drown in several different underlying toolkit's details may sound appealing because you may feel like you're growing your metaphorical market, but what you will almost certainly end up with is doing all toolkits badly instead of even one toolkit well, and that won't be good.

    We've all heard about how the first 90% is 90% of the work, and then the remaining 10% is another 90% of the work. GUIs make that look like hopeless pie-in-the-sky optimism, where the first 10% is 90% of the work, and then the next 10% is ten times the work, and then the next 10% is another ten times the work. Trying to be cross-platform will strangle you.

    However, based on my experience with some similar previous discussions, I don't expect you to immediately buy and agree with my arguments. What I'll really suggest then is to keep what I've said here in the back of your mind, and when you find you're staring down the three toolkits that rigidly require a three-way mutually contradictory way of handling rich text or something, then think back to this post and consider giving yourself permission to drop all but the best-supported and/or most popular underlying toolkit.

  • By iamcalledrob 2024-05-2510:37

    I have been looking for something like this in Go for a while. I think there's a real opportunity for Go to provide a great developer experience for cross platform UI due to how simple the build process is. Speaking from experience, half the pain of cross platform development is managing build complexity, which Go basically eliminates.

    I'm curious how you'll end up solving for cross-platform layout when native controls have different intrinsic sizes per platform?

    This is something I haven't seen solved super well in cross platform toolkits.

    Wishing you luck though.

HackerNews