aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--layout/doc.go15
-rw-r--r--layout/region.go63
-rw-r--r--layout/subimage.go17
-rw-r--r--test/region.go76
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())
+}