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()
}
UseState
hook.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.
spot/ui
, but with a different backend than Cocoa or FLTK?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/ui
and spot
?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.
Explanation of the status column: \ ❓ Not implemented / 🚧 Work in progress / ⚠️ Partially implemented / ✅ Done
Name | Description | Native controls used | Status |
---|---|---|---|
Button | Simple button to initiate an action | Fl_Button NSButton |
✅ |
Checkbox | Control offering the user a choice between two mutually exclusive options | Fl_Check_Button NSButton (NSButtonTypeSwitch) |
✅ |
ComboBox | A combined dropdown menu with text input | ComboBox NSComboBox |
Not started |
Dial | Circular status control | Fl_Dial NSProgressIndicator (with NSCircular style) |
⚠️ |
Dropdown | Drop-down menu to select a single item out of multiple options | Fl_Choice NSComboBox |
✅ |
Image | Control to display bitmap images on | Fl_Box custom NSButton |
✅ |
Label | Simple, non-editable text label | Fl_Box NSTextField |
✅ |
ListBox | Scrollable control which allows the user to select a single or multible items from a given list | Fl_Select_Browser/Fl_Multi_Browser NSTableView |
✅ |
ProgressBar | Progress bar control to visualize the progression of a long-running operation | Fl_Progress NSProgressIndicator |
✅ |
Slider | Horizontal slider input control | Fl_Slider NSSlider |
✅ |
Spinner | Number input control with up/down buttons | Fl_Spinner NSTextField+NSStepper |
✅ |
TextField | Control for single-line text input | Fl_Input NSTextField |
✅ |
TextEditor | General-purpose text box to edit multi-line text content | Fl_Text_Editor NSTextView |
✅ |
Window | Control representing a (top-level) window on the screen | Fl_Window NSWindow |
✅ |