Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions examples/hello-world/hello-world.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,23 @@ func main() {
counter, setCounter := spot.UseState[int](ctx, 0)

buttonTitle := "Click me!"
var window spot.Component
if counter > 0 {
buttonTitle = fmt.Sprintf("Clicked %d times!", counter)
window = &ui.Window{
Title: "Hello World 2",
Width: 200,
Height: 125,
Children: []spot.Component{
&ui.Button{
X: 25, Y: 50, Width: 150, Height: 25,
Title: "Close",
OnClick: func() {
setCounter(0)
},
},
},
}
}

return &ui.Window{
Expand All @@ -30,6 +45,7 @@ func main() {
setCounter(counter + 1)
},
},
window,
},
}
})
Expand Down
54 changes: 43 additions & 11 deletions nodes.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package spot

import "fmt"

type Node struct {
Content Control
Children []Node
Expand All @@ -11,40 +13,54 @@ func (n Node) Mount() {

func (n Node) mount(parent Control) {
if n.Content != nil {
fmt.Printf("Mounting %T[%p]\n", n.Content, n.Content)
n.Content.Mount(parent)
}
for _, child := range n.Children {
fmt.Printf("Mounting child %T[%p] of %T[%p]\n", child.Content, child.Content, n.Content, n.Content)
child.mount(n.Content)
}
}

func (n Node) updateChild(idx int, new Control) {
func (n Node) updateChild(idx int, other Node) {
n.Children[idx].Update(other, n.Content)
return
new := other.Content

old := n.Children[idx].Content
if old == nil && new == nil {
oldEmpty := isEmpty(old)
newEmpty := isEmpty(new)

fmt.Printf("Updating child #%d, %T[%p] <- %T[%p]\n", idx, old, old, new, new)
if oldEmpty && newEmpty {
fmt.Println("Both empty, nothing to do")
return
}

if old != nil && new == nil {
if !oldEmpty && newEmpty {
fmt.Println("Unmounting old child")
if unmountable, ok := old.(Unmountable); ok {
unmountable.Unmount()
}
n.Children[idx].Content = nil
return
}

if old == nil && new != nil {
if oldEmpty && !newEmpty {
fmt.Println("Mounting new child")
n.Children[idx].Content = new
new.Mount(n.Content)
n.Children[idx].mount(n.Content)
return
}

fmt.Println("Updating child")
ok := old.Update(new)
if !ok {
if unmountable, ok := old.(Unmountable); ok {
unmountable.Unmount()
}
n.Children[idx].Content = new
new.Mount(n.Content)
n.Children[idx].mount(n.Content)
}
}

Expand All @@ -55,31 +71,47 @@ func (n Node) Update(other Node, parent Control) {
}
n.Content = nil
} else if n.Content == nil && other.Content != nil {
fmt.Printf("Will replace %T[%p] with %T[%p]\n", n.Content, n.Content, other.Content, other.Content)
n.Content = other.Content
n.Content.Mount(parent)
fmt.Printf("Mounting new Node %T[%p] with parent %T[%p])\n", n.Content, n.Content, parent, parent)
n.mount(parent)
} else if n.Content != nil && other.Content != nil {
fmt.Printf("Will update %T[%p] with new data\n", n.Content, n.Content)
ok := n.Content.Update(other.Content)
fmt.Printf("Update returned %v\n", ok)
if !ok {
if unmountable, ok := n.Content.(Unmountable); ok {
unmountable.Unmount()
}
n.Content = other.Content
n.Content.Mount(parent)
n.mount(parent)
}
}

if len(n.Children) != len(other.Children) {
for idx := range n.Children {
n.updateChild(idx, nil)
n.updateChild(idx, Node{})
}
n.Children = make([]Node, len(other.Children))
for idx := range n.Children {
n.updateChild(idx, other.Children[idx].Content)
n.updateChild(idx, other.Children[idx])
}
return
}

for idx := range n.Children {
n.updateChild(idx, other.Children[idx].Content)
n.updateChild(idx, other.Children[idx])
}
}

func (n Node) Unmount() {
for _, child := range n.Children {
child.Unmount()
}

if n.Content != nil {
if unmountable, ok := n.Content.(Unmountable); ok {
unmountable.Unmount()
}
}
}
24 changes: 22 additions & 2 deletions rendercontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package spot

import (
"fmt"
"reflect"
"strings"
"sync"
)
Expand All @@ -14,10 +15,28 @@ type RenderContext struct {
mutex sync.Mutex
}

// I love you, Go, but I hate you.
func isEmpty(component Component) bool {
if component == nil {
return true
}

v := reflect.ValueOf(component)
k := v.Kind()
switch k {
case reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer,
reflect.UnsafePointer, reflect.Interface, reflect.Slice:
return v.IsNil()
}

return false
}

// BuildNode recursively renders a component and its children into a tree
// of UI controls.
func (ctx *RenderContext) BuildNode(component Component) Node {
if component == nil {
fmt.Printf("[%p] Building node for component %T\n", ctx, component)
if isEmpty(component) {
return Node{}
}

Expand All @@ -38,7 +57,8 @@ func (ctx *RenderContext) BuildNode(component Component) Node {
return Node{Children: list}
}

if container, ok := component.(Container); ok {
if container, ok := component.(Container); ok && container != nil {
fmt.Println("It's a container!")
return container.BuildNode(ctx)
}

Expand Down
26 changes: 26 additions & 0 deletions ui/button_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package ui

import (
"fmt"
"testing"

"github.com/roblillack/spot"
)

func BenchmarkMeaninglessUpdates(b *testing.B) {
node := spot.Build(&Button{})
node.Mount()

for i := 0; i < b.N; i++ {
node.Update(spot.Build(&Button{}), nil)
}
}

func BenchmarkSimpleUpdates(b *testing.B) {
node := spot.Build(&Button{})
node.Mount()

for i := 0; i < b.N; i++ {
node.Update(spot.Build(&Button{Title: fmt.Sprintf("%d", i)}), nil)
}
}
7 changes: 6 additions & 1 deletion ui/window.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package ui

import "github.com/roblillack/spot"
import (
"fmt"

"github.com/roblillack/spot"
)

type Window struct {
Title string
Expand All @@ -20,6 +24,7 @@ func (c *Window) Render(ctx *spot.RenderContext) spot.Component {
}

func (w *Window) BuildNode(ctx *spot.RenderContext) spot.Node {
fmt.Printf("Building %T[%p] for ctx %p\n", w, w, ctx)
kids := []spot.Node{}
for _, child := range w.Children {
kid := ctx.BuildNode(child)
Expand Down
8 changes: 8 additions & 0 deletions ui/window_fltk.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,11 @@ func (w *Window) Mount(parent spot.Control) any {
w.ref.Show()
return w.ref
}

func (w *Window) Unmount() {
if w.ref != nil {
w.ref.Hide()
w.ref.Destroy()
w.ref = nil
}
}