diff options
| author | Sam Anthony <sam@samanthony.xyz> | 2026-02-09 21:01:34 -0500 |
|---|---|---|
| committer | Sam Anthony <sam@samanthony.xyz> | 2026-02-09 21:01:34 -0500 |
| commit | 5fde17eafa11bf397bbf1f18864b8ee26d9a701d (patch) | |
| tree | 6a49da06e7a9ef1faa3c99a4067e561ed077349d | |
| parent | 5de30aee491087fa3b58c8bd629353e1133f28ba (diff) | |
| download | gui-5fde17eafa11bf397bbf1f18864b8ee26d9a701d.zip | |
add region layout
| -rw-r--r-- | layout/doc.go | 15 | ||||
| -rw-r--r-- | layout/region.go | 63 | ||||
| -rw-r--r-- | layout/subimage.go | 17 | ||||
| -rw-r--r-- | test/region.go | 76 |
4 files changed, 171 insertions, 0 deletions
diff --git a/layout/doc.go b/layout/doc.go new file mode 100644 index 0000000..bae4673 --- /dev/null +++ b/layout/doc.go @@ -0,0 +1,15 @@ +/* +Package layout provides means of partitioning screen space. + +A layout exists in a parent Env, and has one or more child Envs. It +acts as a multiplexer for the children. The parent Env may be a window, +another layout, or whatever... Several layers of layouts can be composed. + +A layout allocates screen area to its children by intercepting Resize +events from the parent Env. Upon reception by the layout, a Resize event +is transformed for each child, and forwarded to them. + +Draw calls from the children are intercepted and translated onto their +respective areas before being forwarded to the parent Env. +*/ +package layout diff --git a/layout/region.go b/layout/region.go new file mode 100644 index 0000000..1e92455 --- /dev/null +++ b/layout/region.go @@ -0,0 +1,63 @@ +package layout + +import ( + "image" + "image/draw" + + "github.com/faiface/gui" +) + +// Region is a layout with a single child Env that occupies a sub-area of its parent. +type Region struct { + events <-chan gui.Event + draw chan<- func(draw.Image) image.Rectangle +} + +// NewRegion creates a region layout that occupies part of the parent env's area, as determined by the resize function. +// Resize takes the area of the parent and returns the area of the region. +func NewRegion(env gui.Env, resize func(image.Rectangle) image.Rectangle) gui.Env { + events := make(chan gui.Event) // to child + drw := make(chan func(draw.Image) image.Rectangle) // from child + + go func(events chan<- gui.Event, drw <-chan func(draw.Image) image.Rectangle) { + // Forward first resize event to child + event := <-env.Events() + area := resize(event.(gui.Resize).Rectangle) // first event guaranteed to be Resize + events <- gui.Resize{area} + + for { + select { + case event := <-env.Events(): + switch event := event.(type) { + case gui.Resize: + area = resize(event.Rectangle) + events <- gui.Resize{area} + default: + events <- event + } + case f, ok := <-drw: + if !ok { + close(events) + close(env.Draw()) + return + } + env.Draw() <- drawRegion(f, area) + } + } + }(events, drw) + + return Region{events, drw} +} + +// Translate a draw call to the given area. +func drawRegion(f func(draw.Image) image.Rectangle, area image.Rectangle) func(draw.Image) image.Rectangle { + return func(img draw.Image) image.Rectangle { + return f(subimage(img, area)) + } +} + +// Events implements the Env interface. +func (r Region) Events() <-chan gui.Event { return r.events } + +// Draw implements the Env interface. +func (r Region) Draw() chan<- func(draw.Image) image.Rectangle { return r.draw } diff --git a/layout/subimage.go b/layout/subimage.go new file mode 100644 index 0000000..ecbe931 --- /dev/null +++ b/layout/subimage.go @@ -0,0 +1,17 @@ +package layout + +import ( + "image" + "image/draw" +) + +type subimager interface { + SubImage(image.Rectangle) image.Image +} + +// Subimage returns an image representing the portion of the image m visible through r. +// The returned value shares pixels with the original. +// Panics if the concrete image type does not have a SubImage() method. +func subimage(m draw.Image, r image.Rectangle) draw.Image { + return m.(subimager).SubImage(r).(draw.Image) +} diff --git a/test/region.go b/test/region.go new file mode 100644 index 0000000..afd739e --- /dev/null +++ b/test/region.go @@ -0,0 +1,76 @@ +package main + +import ( + "image" + "image/draw" + "time" + + "github.com/faiface/gui" + "github.com/faiface/gui/layout" + "github.com/faiface/gui/win" + "github.com/faiface/mainthread" +) + +func main() { + mainthread.Run(run) +} + +func run() { + w, err := win.New(win.Title("Grid Layout Test"), win.Resizable()) + if err != nil { + panic(err) + } + + mux, env := gui.NewMux(w) + + // Create region in bottom-right quadrant of window + region := layout.NewRegion(mux.MakeEnv(), func(r image.Rectangle) image.Rectangle { + return image.Rect(r.Min.X+r.Dx()/2, r.Min.Y+r.Dy()/2, r.Max.X, r.Max.Y) + }) + go blinker(region) + + for event := range env.Events() { + switch event.(type) { + case win.WiClose: + close(env.Draw()) + return + } + } +} + +// Blinker is a widget that blinks three times when it is clicked. +func blinker(env gui.Env) { + redraw := func(visible bool) func(draw.Image) image.Rectangle { + return func(img draw.Image) image.Rectangle { + if visible { + draw.Draw(img, img.Bounds(), image.White, image.ZP, draw.Src) + } else { + draw.Draw(img, img.Bounds(), image.Black, image.ZP, draw.Src) + } + return img.Bounds() + } + } + + area := (<-env.Events()).(gui.Resize).Rectangle // first event guaranteed to be Resize + env.Draw() <- redraw(true) + + for event := range env.Events() { + switch event := event.(type) { + case gui.Resize: + area = event.Rectangle + env.Draw() <- redraw(true) + case win.MoDown: + if event.Point.In(area) { + go func() { + for i := 0; i < 3; i++ { + env.Draw() <- redraw(false) + time.Sleep(time.Second / 3) + env.Draw() <- redraw(true) + time.Sleep(time.Second / 3) + } + }() + } + } + } + close(env.Draw()) +} |