roblillack / spot

React-like desktop GUI toolkit for Go
MIT License
1.11k stars 17 forks source link

Add support for "struct components" #1

Closed roblillack closed 5 months ago

roblillack commented 5 months ago

This is a rather substantial changeset to the spot package which includes the following changes:

  1. We now distinguish between Component and Control interfaces, with the latter being components that are mountable to the UI tree. Former components are now controls, and the new component notation is more lightweight as a simple render function is sufficient.
  2. Users of the library are now able to create their own component implementation by simply writing a struct that implements the spot.Component interface which only contains a single function: Render(ctx *spot.RenderContext) spot.Component
  3. As a result of this change, lots of the code is cleaner—even if a bit more complex than before. There's a clear differentiation between rendering components, building a control tree, and mounting controls.
  4. Another change is, that child controls can now ship the code to connect to their parents, whereas before the parent needed to cater to all kinds of different children (see old Cocoa Window implementation)
  5. The Equals() function for controls it not necessary anymore (Probably never was).
  6. Due to moving the ownership of the control tree structure out of the container controls, we can cleanly unmount and replace components if it is necessary due to a state change.
  7. Lastly, the UI control structs are now shared between the different backend which not only is cleaner and less error-prone code, it will improve the documentation tremendously.

So, how does the result look? This is how to create a struct component:

type StructComponent struct {
    X, Y, Width, Height int
    Title               string
}

func (r *StructComponent) Render(ctx *spot.RenderContext) spot.Component {
    counter, setCounter := spot.UseState[int](ctx, 0)

    title := r.Title
    if counter > 0 {
        title = fmt.Sprintf("Clicked %s %dx", r.Title, counter)
    }
    return &ui.Button{
        X: r.X, Y: r.Y, Width: r.Width, Height: r.Height,
        Title: title, OnClick: func() { setCounter(counter + 1) },
    }
}

… and then somewhere else in a render function …

&StructComponent{
    X: 10, Y: 145, Width: 230, Height: 25,
    Title: "Struct component",
},

*chefs kiss*