aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--go.mod12
-rw-r--r--go.sum12
-rw-r--r--mux.go101
-rw-r--r--share.go53
5 files changed, 128 insertions, 53 deletions
diff --git a/.gitignore b/.gitignore
index a639e3c..3dca664 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,5 @@
.vscode
test
+imageviewer
+paint
+pexeso
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..6f9d95c
--- /dev/null
+++ b/go.mod
@@ -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
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..c3ec4ed
--- /dev/null
+++ b/go.sum
@@ -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=
diff --git a/mux.go b/mux.go
index 4198d61..f4eb6d1 100644
--- a/mux.go
+++ b/mux.go
@@ -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)
+}