diff options
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | go.mod | 12 | ||||
| -rw-r--r-- | go.sum | 12 | ||||
| -rw-r--r-- | mux.go | 101 | ||||
| -rw-r--r-- | share.go | 53 |
5 files changed, 128 insertions, 53 deletions
@@ -1,2 +1,5 @@ .vscode test +imageviewer +paint +pexeso @@ -0,0 +1,12 @@ +module github.com/faiface/gui + +go 1.22.4 + +require ( + github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 + github.com/fogleman/gg v1.3.0 + github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 + github.com/go-gl/glfw v0.0.0-20240506104042-037f3cc74f2a + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 + golang.org/x/image v0.19.0 +) @@ -0,0 +1,12 @@ +github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 h1:baVdMKlASEHrj19iqjARrPbaRisD7EuZEVJj6ZMLl1Q= +github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3/go.mod h1:VEPNJUlxl5KdWjDvz6Q1l+rJlxF2i6xqDeGuGAxa87M= +github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA= +github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= +github.com/go-gl/glfw v0.0.0-20240506104042-037f3cc74f2a h1:FAC6eA052T8d4Lp5GR38Sxta8fnq//jjBGDtsT0TVAU= +github.com/go-gl/glfw v0.0.0-20240506104042-037f3cc74f2a/go.mod h1:wyvWpaEu9B/VQiV1jsPs7Mha9I7yto/HqIBw197ZAzk= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +golang.org/x/image v0.19.0 h1:D9FX4QWkLfkeqaC62SonffIIuYdOk/UE2XKUBgRIBIQ= +golang.org/x/image v0.19.0/go.mod h1:y0zrRqlQRWQ5PXaYCOMLTW2fpsxZ8Qh9I/ohnInJEys= @@ -3,17 +3,15 @@ package gui import ( "image" "image/draw" - "sync" ) // Mux can be used to multiplex an Env, let's call it a root Env. Mux implements a way to // create multiple virtual Envs that all interact with the root Env. They receive the same // events and their draw functions get redirected to the root Env. type Mux struct { - mu sync.Mutex - lastResize Event - eventsIns []chan<- Event - draw chan<- func(draw.Image) image.Rectangle + addEventsIn chan<- chan<- Event + size sharedVal[image.Rectangle] + draw chan<- func(draw.Image) image.Rectangle } // NewMux creates a new Mux that multiplexes the given Env. It returns the Mux along with @@ -21,35 +19,51 @@ type Mux struct { // closing the Draw() channel on the master Env closes the whole Mux and all other Envs // created by the Mux. func NewMux(env Env) (mux *Mux, master Env) { + addEventsIn := make(chan chan<- Event) + size := newSharedVal[image.Rectangle]() drawChan := make(chan func(draw.Image) image.Rectangle) - mux = &Mux{draw: drawChan} - master = mux.makeEnv(true) + mux = &Mux{ + addEventsIn: addEventsIn, + size: size, + draw: drawChan, + } go func() { - for d := range drawChan { - env.Draw() <- d - } - close(env.Draw()) - }() + var eventsIns []chan<- Event - go func() { - for e := range env.Events() { - mux.mu.Lock() - if resize, ok := e.(Resize); ok { - mux.lastResize = resize + defer close(env.Draw()) + defer close(addEventsIn) + defer size.close() + defer func() { + for _, eventsIn := range eventsIns { + close(eventsIn) } - for _, eventsIn := range mux.eventsIns { - eventsIn <- e + }() + + for { + select { + case d, ok := <-drawChan: + if !ok { // closed by master env + return + } + env.Draw() <- d + case e, ok := <-env.Events(): + if !ok { + return + } + if resize, ok := e.(Resize); ok { + size.set <- resize.Rectangle + } + for _, eventsIn := range eventsIns { + eventsIn <- e + } + case eventsIn := <-addEventsIn: + eventsIns = append(eventsIns, eventsIn) } - mux.mu.Unlock() } - mux.mu.Lock() - for _, eventsIn := range mux.eventsIns { - close(eventsIn) - } - mux.mu.Unlock() }() + master = mux.makeEnv(true) return mux, master } @@ -73,14 +87,9 @@ func (mux *Mux) makeEnv(master bool) Env { drawChan := make(chan func(draw.Image) image.Rectangle) env := &muxEnv{eventsOut, drawChan} - mux.mu.Lock() - mux.eventsIns = append(mux.eventsIns, eventsIn) - // make sure to always send a resize event to a new Env if we got the size already - // that means it missed the resize event by the root Env - if mux.lastResize != nil { - eventsIn <- mux.lastResize - } - mux.mu.Unlock() + mux.addEventsIn <- eventsIn + // make sure to always send a resize event to a new Env + eventsIn <- Resize{mux.size.get()} go func() { func() { @@ -99,8 +108,7 @@ func (mux *Mux) makeEnv(master bool) Env { // commands, correctly draining the Env until it closes itself. defer func() { if recover() != nil { - for range drawChan { - } + drain(drawChan) } }() for d := range drawChan { @@ -108,27 +116,14 @@ func (mux *Mux) makeEnv(master bool) Env { } }() if master { - mux.mu.Lock() - for _, eventsIn := range mux.eventsIns { - close(eventsIn) - } - mux.eventsIns = nil close(mux.draw) - mux.mu.Unlock() - } else { - mux.mu.Lock() - i := -1 - for i = range mux.eventsIns { - if mux.eventsIns[i] == eventsIn { - break - } - } - if i != -1 { - mux.eventsIns = append(mux.eventsIns[:i], mux.eventsIns[i+1:]...) - } - mux.mu.Unlock() } }() return env } + +func drain[T any](c <-chan T) { + for range c { + } +} diff --git a/share.go b/share.go new file mode 100644 index 0000000..36b903b --- /dev/null +++ b/share.go @@ -0,0 +1,53 @@ +package gui + +// sharedVal is a concurrent interface to a piece of shared data. +// +// A client can read the data by sending a channel via request, and the stored value will +// be sent back via the channel. The client is responsible for closing the channel. +// +// The stored value can be changed by sending the new value via set. Requests block until +// the first value is received on set. +// +// A sharedVal should be closed after use. +type sharedVal[T any] struct { + request chan<- chan T + set chan<- T +} + +func newSharedVal[T any]() sharedVal[T] { + request := make(chan chan T) + set := make(chan T) + go func() { + val := <-set // wait for initial value + for { + select { + case v, ok := <-set: + if !ok { // closed + return + } + val = v + case req, ok := <-request: + if !ok { // closed + return + } + go func() { // don't wait for client to receive + req <- val + }() + } + } + }() + return sharedVal[T]{request, set} +} + +// get makes a synchronous request and returns the stored value. +func (sv sharedVal[T]) get() T { + c := make(chan T) + defer close(c) + sv.request <- c + return <-c +} + +func (sv sharedVal[T]) close() { + close(sv.request) + close(sv.set) +} |