diff options
| -rw-r--r-- | env.go | 12 | ||||
| -rw-r--r-- | event.go | 46 | ||||
| -rw-r--r-- | go.mod | 2 | ||||
| -rw-r--r-- | gui_test.go | 15 | ||||
| -rw-r--r-- | layout_test.go | 4 | ||||
| -rw-r--r-- | mux.go | 17 | ||||
| -rw-r--r-- | mux_test.go | 2 | ||||
| -rw-r--r-- | win.go | 43 |
8 files changed, 47 insertions, 94 deletions
@@ -3,6 +3,8 @@ package gui import ( "image" "image/draw" + + "git.samanthony.xyz/share" ) // Env is the most important thing in this package. It is an interactive graphical @@ -15,7 +17,7 @@ import ( // // An Env guarantees to produce a "resize/<x0>/<y0>/<x1>/<y1>" event as its first event. // -// The Events() channel must be unlimited in capacity. Use makeEventsChan() to create +// The Events() channel must be unlimited in capacity. Use share.Queue to create // a channel of events with an unlimited capacity. // // The Draw() channel may be synchronous. @@ -53,7 +55,7 @@ func newEnv(parent Env, filterDraws func(func(draw.Image) image.Rectangle, chan<- func(draw.Image) image.Rectangle), shutdown func(), ) Env { - eventsOut, eventsIn := makeEventsChan() + events := share.NewQueue[Event]() drawChan := make(chan func(draw.Image) image.Rectangle) child := newAttachHandler() kill := make(chan bool) @@ -70,7 +72,7 @@ func newEnv(parent Env, close(detachFromParent) }() defer shutdown() - defer close(eventsIn) + defer close(events.Enqueue) defer close(drawChan) defer close(kill) defer func() { @@ -82,7 +84,7 @@ func newEnv(parent Env, for { select { case e := <-parent.Events(): - filterEvents(e, eventsIn) + filterEvents(e, events.Enqueue) case d := <-drawChan: filterDraws(d, parent.Draw()) case <-kill: @@ -92,7 +94,7 @@ func newEnv(parent Env, }() e := env{ - events: eventsOut, + events: events.Dequeue, draw: drawChan, attachChan: child.attach(), kill: kill, @@ -23,52 +23,6 @@ func (r Resize) String() string { return fmt.Sprintf("resize/%d/%d/%d/%d", r.Min.X, r.Min.Y, r.Max.X, r.Max.Y) } -// makeEventsChan implements a channel of events with an unlimited capacity. It does so -// by creating a goroutine that queues incoming events. Sending to this channel never blocks -// and no events get lost. -// -// The unlimited capacity channel is very suitable for delivering events because the consumer -// may be unavailable for some time (doing a heavy computation), but will get to the events -// later. -// -// An unlimited capacity channel has its dangers in general, but is completely fine for -// the purpose of delivering events. This is because the production of events is fairly -// infrequent and should never out-run their consumption in the long term. -func makeEventsChan() (<-chan Event, chan<- Event) { - out, in := make(chan Event), make(chan Event) - - go func() { - var queue []Event - - for { - x, ok := <-in - if !ok { - close(out) - return - } - queue = append(queue, x) - - for len(queue) > 0 { - select { - case out <- queue[0]: - queue = queue[1:] - case x, ok := <-in: - if !ok { - for _, x := range queue { - out <- x - } - close(out) - return - } - queue = append(queue, x) - } - } - } - }() - - return out, in -} - // Button indicates a mouse button in an event. type Button string @@ -3,7 +3,7 @@ module github.com/faiface/gui go 1.22.4 require ( - git.samanthony.xyz/share v0.0.0 + git.samanthony.xyz/share v0.2.0 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 diff --git a/gui_test.go b/gui_test.go index 0d18c92..c8e685e 100644 --- a/gui_test.go +++ b/gui_test.go @@ -4,6 +4,8 @@ import ( "image" "image/draw" "time" + + "git.samanthony.xyz/share" ) const timeout = 1 * time.Second @@ -31,8 +33,7 @@ func tryRecv[T any](c <-chan T, timeout time.Duration) (*T, bool) { } type dummyEnv struct { - eventsIn chan<- Event - eventsOut <-chan Event + events share.Queue[Event] drawIn chan<- func(draw.Image) image.Rectangle drawOut <-chan func(draw.Image) image.Rectangle @@ -44,7 +45,7 @@ type dummyEnv struct { } func newDummyEnv(size image.Rectangle) dummyEnv { - eventsOut, eventsIn := makeEventsChan() + events := share.NewQueue[Event]() drawIn := make(chan func(draw.Image) image.Rectangle) drawOut := make(chan func(draw.Image) image.Rectangle) kill := make(chan bool) @@ -60,7 +61,7 @@ func newDummyEnv(size image.Rectangle) dummyEnv { defer close(kill) defer close(drawOut) defer close(drawIn) - defer close(eventsIn) + defer close(events.Enqueue) defer func() { go drain(drawIn) attached.kill <- true @@ -77,13 +78,13 @@ func newDummyEnv(size image.Rectangle) dummyEnv { } }() - eventsIn <- Resize{size} + events.Enqueue <- Resize{size} - return dummyEnv{eventsIn, eventsOut, drawIn, drawOut, kill, dead, attached.attach()} + return dummyEnv{events, drawIn, drawOut, kill, dead, attached.attach()} } func (de dummyEnv) Events() <-chan Event { - return de.eventsOut + return de.events.Dequeue } func (de dummyEnv) Draw() chan<- func(draw.Image) image.Rectangle { diff --git a/layout_test.go b/layout_test.go index 1999c63..4a78092 100644 --- a/layout_test.go +++ b/layout_test.go @@ -33,7 +33,7 @@ func TestSniffer(t *testing.T) { // Send events to sniffer. events := []Event{dummyEvent{"barEvent"}, dummyEvent{"fooEvent"}} for _, event := range events { - root.eventsIn <- event + root.events.Enqueue <- event eventp, ok := tryRecv(sniffer.Events(), timeout) if !ok { @@ -91,6 +91,6 @@ func TestResizer(t *testing.T) { } // this event should be replaced by the resizer - root.eventsIn <- Resize{image.Rectangle{}} + root.events.Enqueue <- Resize{image.Rectangle{}} } } @@ -66,7 +66,7 @@ func NewMux(parent Env) Mux { size.Set <- resize.Rectangle } for _, child := range children { - child.eventsIn <- e + child.events.Enqueue <- e } case child := <-addChild: children = append(children, child) @@ -108,8 +108,7 @@ func (mux Mux) detach() <-chan bool { } type muxEnv struct { - eventsIn chan<- Event - eventsOut <-chan Event + events share.Queue[Event] draw chan<- func(draw.Image) image.Rectangle attachChan chan<- attachable kill chan<- bool @@ -118,7 +117,7 @@ type muxEnv struct { } func (mux Mux) MakeEnv() Env { - eventsOut, eventsIn := makeEventsChan() + events := share.NewQueue[Event]() drawChan := make(chan func(draw.Image) image.Rectangle) attached := newAttachHandler() kill := make(chan bool) @@ -126,8 +125,7 @@ func (mux Mux) MakeEnv() Env { detachFromMux := make(chan bool) env := muxEnv{ - eventsIn: eventsIn, - eventsOut: eventsOut, + events: events, draw: drawChan, attachChan: attached.attach(), kill: kill, @@ -136,7 +134,7 @@ func (mux Mux) MakeEnv() Env { } mux.addChild <- env // make sure to always send a resize event to a new Env - eventsIn <- Resize{mux.size.Get()} + events.Enqueue <- Resize{mux.size.Get()} go func() { defer func() { @@ -145,8 +143,7 @@ func (mux Mux) MakeEnv() Env { }() defer close(kill) defer close(drawChan) - defer close(eventsIn) - // eventsOut closed automatically by makeEventsChan() + defer close(events.Enqueue) defer func() { mux.removeChild <- env @@ -174,7 +171,7 @@ func (mux Mux) MakeEnv() Env { } func (env muxEnv) Events() <-chan Event { - return env.eventsOut + return env.events.Dequeue } func (env muxEnv) Draw() chan<- func(draw.Image) image.Rectangle { diff --git a/mux_test.go b/mux_test.go index 75f6cac..4736641 100644 --- a/mux_test.go +++ b/mux_test.go @@ -25,7 +25,7 @@ func TestMuxEvent(t *testing.T) { events := []Event{Resize{rect}, dummyEvent{"fooEvent"}, dummyEvent{"barEvent"}, dummyEvent{"bazEvent"}} go func() { for _, event := range events[1:] { // skip resize—it's sent automatically by the root Env - root.eventsIn <- event + root.events.Enqueue <- event } }() @@ -7,6 +7,7 @@ import ( "time" "unsafe" + "git.samanthony.xyz/share" "github.com/faiface/mainthread" "github.com/go-gl/gl/v2.1/gl" "github.com/go-gl/glfw/v3.2/glfw" @@ -75,14 +76,13 @@ func New(opts ...Option) (*Win, error) { opt(&o) } - eventsOut, eventsIn := makeEventsChan() + events := share.NewQueue[Event]() w := &Win{ - eventsOut: eventsOut, - eventsIn: eventsIn, - draw: make(chan func(draw.Image) image.Rectangle), - newSize: make(chan image.Rectangle), - finish: make(chan struct{}), + events: events, + draw: make(chan func(draw.Image) image.Rectangle), + newSize: make(chan image.Rectangle), + finish: make(chan struct{}), } var err error @@ -157,9 +157,8 @@ func makeGLFWWin(o *options) (*glfw.Window, error) { // // Warning: only one window can be open at a time. This will be fixed. type Win struct { - eventsOut <-chan Event - eventsIn chan<- Event - draw chan func(draw.Image) image.Rectangle + events share.Queue[Event] + draw chan func(draw.Image) image.Rectangle newSize chan image.Rectangle finish chan struct{} @@ -170,7 +169,7 @@ type Win struct { } // Events returns the events channel of the window. -func (w *Win) Events() <-chan Event { return w.eventsOut } +func (w *Win) Events() <-chan Event { return w.events.Dequeue } // Draw returns the draw channel of the window. func (w *Win) Draw() chan<- func(draw.Image) image.Rectangle { return w.draw } @@ -209,7 +208,7 @@ func (w *Win) eventThread() { w.w.SetCursorPosCallback(func(_ *glfw.Window, x, y float64) { moX, moY = int(x), int(y) - w.eventsIn <- MoMove{image.Pt(moX*w.ratio, moY*w.ratio)} + w.events.Enqueue <- MoMove{image.Pt(moX*w.ratio, moY*w.ratio)} }) w.w.SetMouseButtonCallback(func(_ *glfw.Window, button glfw.MouseButton, action glfw.Action, mod glfw.ModifierKey) { @@ -219,18 +218,18 @@ func (w *Win) eventThread() { } switch action { case glfw.Press: - w.eventsIn <- MoDown{image.Pt(moX*w.ratio, moY*w.ratio), b} + w.events.Enqueue <- MoDown{image.Pt(moX*w.ratio, moY*w.ratio), b} case glfw.Release: - w.eventsIn <- MoUp{image.Pt(moX*w.ratio, moY*w.ratio), b} + w.events.Enqueue <- MoUp{image.Pt(moX*w.ratio, moY*w.ratio), b} } }) w.w.SetScrollCallback(func(_ *glfw.Window, xoff, yoff float64) { - w.eventsIn <- MoScroll{image.Pt(int(xoff), int(yoff))} + w.events.Enqueue <- MoScroll{image.Pt(int(xoff), int(yoff))} }) w.w.SetCharCallback(func(_ *glfw.Window, r rune) { - w.eventsIn <- KbType{r} + w.events.Enqueue <- KbType{r} }) w.w.SetKeyCallback(func(_ *glfw.Window, key glfw.Key, _ int, action glfw.Action, _ glfw.ModifierKey) { @@ -240,31 +239,31 @@ func (w *Win) eventThread() { } switch action { case glfw.Press: - w.eventsIn <- KbDown{k} + w.events.Enqueue <- KbDown{k} case glfw.Release: - w.eventsIn <- KbUp{k} + w.events.Enqueue <- KbUp{k} case glfw.Repeat: - w.eventsIn <- KbRepeat{k} + w.events.Enqueue <- KbRepeat{k} } }) w.w.SetFramebufferSizeCallback(func(_ *glfw.Window, width, height int) { r := image.Rect(0, 0, width, height) w.newSize <- r - w.eventsIn <- Resize{Rectangle: r} + w.events.Enqueue <- Resize{Rectangle: r} }) w.w.SetCloseCallback(func(_ *glfw.Window) { - w.eventsIn <- WiClose{} + w.events.Enqueue <- WiClose{} }) r := w.img.Bounds() - w.eventsIn <- Resize{Rectangle: r} + w.events.Enqueue <- Resize{Rectangle: r} for { select { case <-w.finish: - close(w.eventsIn) + close(w.events.Enqueue) w.w.Destroy() return default: |