aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSam Anthony <sam@samanthony.xyz>2026-02-11 17:42:51 -0500
committerSam Anthony <sam@samanthony.xyz>2026-02-11 17:42:51 -0500
commitc8610ea4164403c418c3fc13aee13685aeff447f (patch)
tree06e0d4b2012b791fd6bec059d6cde0fd22a3d5e1
parentebc5d96f0614ab3b4f2073eadf22fbe554b0ec8f (diff)
downloadgui-c8610ea4164403c418c3fc13aee13685aeff447f.zip
add border layout
-rw-r--r--layout/border.go234
-rw-r--r--layout/geo.go97
-rw-r--r--layout/geo_test.go98
-rw-r--r--layout/region.go45
-rw-r--r--layout/resize.go35
-rw-r--r--layout/resize_test.go58
-rw-r--r--layout/rows.go6
-rw-r--r--test/border/main.go95
-rw-r--r--test/region/main.go8
-rw-r--r--test/rows/main.go2
10 files changed, 555 insertions, 123 deletions
diff --git a/layout/border.go b/layout/border.go
new file mode 100644
index 0000000..c60e602
--- /dev/null
+++ b/layout/border.go
@@ -0,0 +1,234 @@
+package layout
+
+import (
+ "fmt"
+ "image"
+ "image/color"
+ "image/draw"
+
+ "github.com/faiface/gui"
+)
+
+// NewBorder creates a layout with a margin, border, and padding drawn around itself.
+func NewBorder(env gui.Env, opts ...BorderOption) gui.Env {
+ o := evalBorderOptions(opts...)
+
+ events := make(chan gui.Event) // to child
+ draws := make(chan func(draw.Image) image.Rectangle) // from child
+
+ go func(events chan<- gui.Event, draws <-chan func(draw.Image) image.Rectangle) {
+ defer close(env.Draw())
+ defer close(events)
+
+ redraw := func(area image.Rectangle) {
+ env.Draw() <- drawBorder(o)
+ fmt.Println("area, content:", area, borderContentArea(area, o))
+ events <- gui.Resize{borderContentArea(area, o)} // translate and forward to child
+ }
+
+ // Forward first resize event to child and draw border
+ event := (<-env.Events()).(gui.Resize) // first event guaranteed to be Resize
+ area := event.Rectangle
+ go redraw(area)
+
+ for {
+ select {
+ case event := <-env.Events(): // event from parent
+ switch event := event.(type) {
+ case gui.Resize:
+ area = event.Rectangle
+ go redraw(area)
+ default:
+ go func() { events <- event }() // forward to child
+ }
+ case f, ok := <-draws: // draw call from child
+ if !ok {
+ return
+ }
+ env.Draw() <- drawSubImage(f, borderContentArea(area, o))
+ }
+ }
+ }(events, draws)
+
+ return NewRegion(borderEnv{events, draws}, color.Transparent, Full())
+}
+
+// BorderContentArea returns the drawable area inside the margin,
+// border, and padding of a border layout, given the overall area
+// of the layout and the options.
+func borderContentArea(area image.Rectangle, o borderOptions) image.Rectangle {
+ top := o.margin.top + o.border.top + o.padding.top
+ right := o.margin.right + o.border.right + o.padding.right
+ bottom := o.margin.bottom + o.border.bottom + o.padding.bottom
+ left := o.margin.left + o.border.left + o.padding.left
+ return Inset(top, right, bottom, left)(area)
+}
+
+func drawBorder(o borderOptions) func(draw.Image) image.Rectangle {
+ return func(img draw.Image) image.Rectangle {
+ r := Inset(o.margin.top, o.margin.right, o.margin.bottom, o.margin.left)(img.Bounds())
+ mask := image.NewAlpha(image.Rect(0, 0, r.Dx(), r.Dy()))
+ draw.Draw(mask, mask.Bounds(), image.Opaque, image.ZP, draw.Src) // mask border
+ draw.Draw(mask, Inset(o.border.top, o.border.right, o.border.bottom, o.border.left)(mask.Bounds()), image.Transparent, image.ZP, draw.Src) // unmask inside border
+ draw.DrawMask(img, r, &image.Uniform{o.Color}, image.ZP, mask, image.ZP, draw.Over)
+ fmt.Println("border:", r)
+ return r
+ }
+}
+
+// BorderOption is a functional option for the NewBorder constructor.
+type BorderOption func(o *borderOptions)
+
+type borderOptions struct {
+ margin, border, padding sides
+ color.Color
+}
+
+type sides struct{ top, right, bottom, left int }
+
+// Margin option sets the margin size, in pixels, on all sides.
+func Margin(px int) BorderOption {
+ if px < 0 {
+ panic(fmt.Sprintf("margin %d < 0", px))
+ }
+ return func(o *borderOptions) { o.margin = sides{px, px, px, px} }
+}
+
+// MarginTop option sets the top margin size, in pixels.
+func MarginTop(px int) BorderOption {
+ if px < 0 {
+ panic(fmt.Sprintf("margin %d < 0", px))
+ }
+ return func(o *borderOptions) { o.margin.top = px }
+}
+
+// MarginRight option sets the right margin size, in pixels.
+func MarginRight(px int) BorderOption {
+ if px < 0 {
+ panic(fmt.Sprintf("margin %d < 0", px))
+ }
+ return func(o *borderOptions) { o.margin.right = px }
+}
+
+// MarginBottom option sets the bottom margin size, in pixels.
+func MarginBottom(px int) BorderOption {
+ if px < 0 {
+ panic(fmt.Sprintf("margin %d < 0", px))
+ }
+ return func(o *borderOptions) { o.margin.bottom = px }
+}
+
+// MarginLeft option sets the left margin size, in pixels.
+func MarginLeft(px int) BorderOption {
+ if px < 0 {
+ panic(fmt.Sprintf("margin %d < 0", px))
+ }
+ return func(o *borderOptions) { o.margin.left = px }
+}
+
+// Border option sets the border thickness (in pixels) and color on all sides.
+func Border(thick int, c color.Color) BorderOption {
+ if thick < 0 {
+ panic(fmt.Sprintf("border thickness %d < 0", thick))
+ }
+ return func(o *borderOptions) {
+ o.border = sides{thick, thick, thick, thick}
+ o.Color = c
+ }
+}
+
+// BorderColor sets the color of the border.
+func BorderColor(c color.Color) BorderOption {
+ return func(o *borderOptions) { o.Color = c }
+}
+
+// BorderTop sets the top border thickness, in pixels.
+func BorderTop(thick int) BorderOption {
+ if thick < 0 {
+ panic(fmt.Sprintf("border thickness %d < 0", thick))
+ }
+ return func(o *borderOptions) { o.border.top = thick }
+}
+
+// BorderRight sets the right border thickness, in pixels.
+func BorderRight(thick int) BorderOption {
+ if thick < 0 {
+ panic(fmt.Sprintf("border thickness %d < 0", thick))
+ }
+ return func(o *borderOptions) { o.border.right = thick }
+}
+
+// BorderBottom sets the bottom border thickness, in pixels.
+func BorderBottom(thick int) BorderOption {
+ if thick < 0 {
+ panic(fmt.Sprintf("border thickness %d < 0", thick))
+ }
+ return func(o *borderOptions) { o.border.bottom = thick }
+}
+
+// BorderLeft sets the left border thickness, in pixels.
+func BorderLeft(thick int) BorderOption {
+ if thick < 0 {
+ panic(fmt.Sprintf("border thickness %d < 0", thick))
+ }
+ return func(o *borderOptions) { o.border.left = thick }
+}
+
+// Padding option sets the padding size, in pixels, on all sides.
+func Padding(px int) BorderOption {
+ if px < 0 {
+ panic(fmt.Sprintf("padding %d < 0", px))
+ }
+ return func(o *borderOptions) { o.padding = sides{px, px, px, px} }
+}
+
+// PaddingTop option sets the top padding size, in pixels.
+func PaddingTop(px int) BorderOption {
+ if px < 0 {
+ panic(fmt.Sprintf("padding %d < 0", px))
+ }
+ return func(o *borderOptions) { o.padding.top = px }
+}
+
+// PaddingRight option sets the right padding size, in pixels.
+func PaddingRight(px int) BorderOption {
+ if px < 0 {
+ panic(fmt.Sprintf("padding %d < 0", px))
+ }
+ return func(o *borderOptions) { o.padding.right = px }
+}
+
+// PaddingBottom option sets the bottom padding size, in pixels.
+func PaddingBottom(px int) BorderOption {
+ if px < 0 {
+ panic(fmt.Sprintf("padding %d < 0", px))
+ }
+ return func(o *borderOptions) { o.padding.bottom = px }
+}
+
+// PaddingLeft option sets the left padding size, in pixels.
+func PaddingLeft(px int) BorderOption {
+ if px < 0 {
+ panic(fmt.Sprintf("padding %d < 0", px))
+ }
+ return func(o *borderOptions) { o.padding.left = px }
+}
+
+func evalBorderOptions(opts ...BorderOption) borderOptions {
+ o := borderOptions{}
+ for _, opt := range opts {
+ opt(&o)
+ }
+ return o
+}
+
+type borderEnv struct {
+ events <-chan gui.Event
+ draw chan<- func(draw.Image) image.Rectangle
+}
+
+// Events implements the Env interface.
+func (b borderEnv) Events() <-chan gui.Event { return b.events }
+
+// Draw implements the Env interface.
+func (b borderEnv) Draw() chan<- func(draw.Image) image.Rectangle { return b.draw }
diff --git a/layout/geo.go b/layout/geo.go
new file mode 100644
index 0000000..6f84049
--- /dev/null
+++ b/layout/geo.go
@@ -0,0 +1,97 @@
+package layout
+
+import (
+ "fmt"
+ "image"
+)
+
+// ResizeFunc is a function that computes the area of a layout when it receives a Resize event.
+// It takes the Resize's Rectangle and returns the area that the layout should cover.
+type ResizeFunc func(image.Rectangle) image.Rectangle
+
+// Full resizes a layout to consume all of its parent's area.
+func Full() ResizeFunc {
+ return func(r image.Rectangle) image.Rectangle { return r }
+}
+
+// Quad1 resizes a layout to consume the top-right quadrant of its parent.
+func Quad1() ResizeFunc {
+ return func(r image.Rectangle) image.Rectangle {
+ mid := midpoint(r)
+ return image.Rect(mid.X, r.Min.Y, r.Max.X, mid.Y)
+ }
+}
+
+// Quad2 resizes a layout to consume the top-left quadrant of its parent.
+func Quad2() ResizeFunc {
+ return func(r image.Rectangle) image.Rectangle {
+ mid := midpoint(r)
+ return image.Rectangle{r.Min, mid}
+ }
+}
+
+// Quad3 resizes a layout to consume the bottom-left quadrant of its parent.
+func Quad3() ResizeFunc {
+ return func(r image.Rectangle) image.Rectangle {
+ mid := midpoint(r)
+ return image.Rect(r.Min.X, mid.Y, mid.X, r.Max.Y)
+ }
+}
+
+// Quad4 resizes a layout to consume the bottom-right quadrant of its parent.
+func Quad4() ResizeFunc {
+ return func(r image.Rectangle) image.Rectangle {
+ mid := midpoint(r)
+ return image.Rectangle{mid, r.Max}
+ }
+}
+
+// Inset resizes a layout to be inset some number of pixels from the edges of its parent.
+func Inset(top, right, bottom, left int) ResizeFunc {
+ if top < 0 {
+ panic(fmt.Sprintf("inset %d < 0", top))
+ } else if right < 0 {
+ panic(fmt.Sprintf("inset %d < 0", right))
+ } else if bottom < 0 {
+ panic(fmt.Sprintf("inset %d < 0", bottom))
+ } else if left < 0 {
+ panic(fmt.Sprintf("inset %d < 0", left))
+ }
+ return func(r image.Rectangle) image.Rectangle {
+ if r.Dx() < left+right {
+ r.Min.X = (r.Min.X + r.Max.X) / 2
+ r.Max.X = r.Min.X
+ } else {
+ r.Min.X += left
+ r.Max.X -= right
+ }
+ if r.Dy() < top+bottom {
+ r.Min.Y = (r.Min.Y + r.Max.Y) / 2
+ r.Max.Y = r.Min.Y
+ } else {
+ r.Min.Y += top
+ r.Max.Y -= bottom
+ }
+ return r
+ }
+}
+
+// InsetAll resizes as layout to be inset px pixels on all sides from the edges of its parent.
+func InsetAll(px int) ResizeFunc { return Inset(px, px, px, px) }
+
+// InsetTop resizes a layout to be inset px pixels from the top edge of its parent.
+func InsetTop(px int) ResizeFunc { return Inset(px, 0, 0, 0) }
+
+// InsetRight resizes a layout to be inset px pixels from the top edge of its parent.
+func InsetRight(px int) ResizeFunc { return Inset(0, px, 0, 0) }
+
+// InsetBottom resizes a layout to be inset px pixels from the top edge of its parent.
+func InsetBottom(px int) ResizeFunc { return Inset(0, 0, px, 0) }
+
+// InsetLeft resizes a layout to be inset px pixels from the top edge of its parent.
+func InsetLeft(px int) ResizeFunc { return Inset(0, 0, 0, px) }
+
+// midpoint returns the point in the middle of a rectangle.
+func midpoint(r image.Rectangle) image.Point {
+ return r.Min.Add(r.Max).Div(2)
+}
diff --git a/layout/geo_test.go b/layout/geo_test.go
new file mode 100644
index 0000000..7dfc864
--- /dev/null
+++ b/layout/geo_test.go
@@ -0,0 +1,98 @@
+package layout_test
+
+import (
+ "image"
+ "testing"
+
+ "github.com/faiface/gui/layout"
+)
+
+func TestFull(t *testing.T) {
+ t.Parallel()
+ parent := image.Rect(111, 222, 333, 444)
+ want := parent
+ testResize(t, layout.Full(), parent, want)
+}
+
+func TestQuad1(t *testing.T) {
+ t.Parallel()
+ parent := image.Rect(111, 222, 333, 444)
+ want := image.Rect(222, 222, 333, 333)
+ testResize(t, layout.Quad1(), parent, want)
+}
+
+func TestQuad2(t *testing.T) {
+ t.Parallel()
+ parent := image.Rect(111, 222, 333, 444)
+ want := image.Rect(111, 222, 222, 333)
+ testResize(t, layout.Quad2(), parent, want)
+}
+
+func TestQuad3(t *testing.T) {
+ t.Parallel()
+ parent := image.Rect(111, 222, 333, 444)
+ want := image.Rect(111, 333, 222, 444)
+ testResize(t, layout.Quad3(), parent, want)
+}
+
+func TestQuad4(t *testing.T) {
+ t.Parallel()
+ parent := image.Rect(111, 222, 333, 444)
+ want := image.Rect(222, 333, 333, 444)
+ testResize(t, layout.Quad4(), parent, want)
+}
+
+func TestInset(t *testing.T) {
+ t.Parallel()
+ parent := image.Rect(111, 222, 333, 444)
+ resize := layout.Inset(1, 2, 3, 4)
+ want := image.Rect(115, 223, 331, 441)
+ testResize(t, resize, parent, want)
+}
+
+func TestInsetAll(t *testing.T) {
+ t.Parallel()
+ parent := image.Rect(111, 222, 333, 444)
+ resize := layout.InsetAll(5)
+ want := parent.Inset(5)
+ testResize(t, resize, parent, want)
+}
+
+func TestInsetTop(t *testing.T) {
+ t.Parallel()
+ parent := image.Rect(111, 222, 333, 444)
+ resize := layout.InsetTop(2)
+ want := image.Rect(111, 224, 333, 444)
+ testResize(t, resize, parent, want)
+}
+
+func TestInsetRight(t *testing.T) {
+ t.Parallel()
+ parent := image.Rect(111, 222, 333, 444)
+ resize := layout.InsetRight(3)
+ want := image.Rect(111, 222, 330, 444)
+ testResize(t, resize, parent, want)
+}
+
+func TestInsetBottom(t *testing.T) {
+ t.Parallel()
+ parent := image.Rect(111, 222, 333, 444)
+ resize := layout.InsetBottom(4)
+ want := image.Rect(111, 222, 333, 440)
+ testResize(t, resize, parent, want)
+}
+
+func TestInsetLeft(t *testing.T) {
+ t.Parallel()
+ parent := image.Rect(111, 222, 333, 444)
+ resize := layout.InsetLeft(1)
+ want := image.Rect(112, 222, 333, 444)
+ testResize(t, resize, parent, want)
+}
+
+func testResize(t *testing.T, f layout.ResizeFunc, r, want image.Rectangle) {
+ r1 := f(r)
+ if r1 != want {
+ t.Errorf("resize(%v) = %v; want %v", r, r1, want)
+ }
+}
diff --git a/layout/region.go b/layout/region.go
index 1e4d4b1..08fa48d 100644
--- a/layout/region.go
+++ b/layout/region.go
@@ -17,45 +17,46 @@ type Region struct {
// 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.
// It returns the child Env.
-func NewRegion(env gui.Env, clr color.Color, 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
+func NewRegion(env gui.Env, clr color.Color, resize ResizeFunc) gui.Env {
+ events := make(chan gui.Event) // to child
+ draws := 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}
+ go func(events chan<- gui.Event, draws <-chan func(draw.Image) image.Rectangle) {
+ defer close(env.Draw())
+ defer close(events)
- // Draw background
- redrawBg := func(area image.Rectangle) func(draw.Image) image.Rectangle {
- return drawSubImage(drawBackground(clr), area)
+ redraw := func(area, childArea image.Rectangle) {
+ env.Draw() <- drawSubImage(drawBackground(clr), area)
+ events <- gui.Resize{childArea}
}
- env.Draw() <- redrawBg(area)
+
+ // Draw background and forward first resize event to child
+ event := (<-env.Events()).(gui.Resize) // first event guaranteed to be Resize
+ area := event.Rectangle
+ childArea := resize(area)
+ go redraw(area, childArea)
for {
select {
case event := <-env.Events(): // event from parent
switch event := event.(type) {
case gui.Resize:
- env.Draw() <- redrawBg(area)
- area = resize(event.Rectangle)
- events <- gui.Resize{area} // forward to child
+ area = event.Rectangle
+ childArea = resize(area)
+ go redraw(area, childArea)
default:
- events <- event
+ go func() { events <- event }() // forward to child
}
- case f, ok := <-drw: // draw call from child
+ case f, ok := <-draws: // draw call from child
if !ok {
- close(events)
- close(env.Draw())
return
}
- env.Draw() <- drawSubImage(f, area)
+ env.Draw() <- drawSubImage(f, childArea)
}
}
- }(events, drw)
+ }(events, draws)
- return Region{events, drw}
+ return Region{events, draws}
}
// Events implements the Env interface.
diff --git a/layout/resize.go b/layout/resize.go
deleted file mode 100644
index 1d3d6f1..0000000
--- a/layout/resize.go
+++ /dev/null
@@ -1,35 +0,0 @@
-package layout
-
-import "image"
-
-// ResizeAll resizes a layout to consume all of its parent's area.
-func ResizeAll(r image.Rectangle) image.Rectangle { return r }
-
-// ResizeQuad1 resizes a layout to consume the top-right quadrant of its parent.
-func ResizeQuad1(r image.Rectangle) image.Rectangle {
- mid := midpoint(r)
- return image.Rect(mid.X, r.Min.Y, r.Max.X, mid.Y)
-}
-
-// ResizeQuad2 resizes a layout to consume the top-left quadrant of its parent.
-func ResizeQuad2(r image.Rectangle) image.Rectangle {
- mid := midpoint(r)
- return image.Rectangle{r.Min, mid}
-}
-
-// ResizeQuad3 resizes a layout to consume the bottom-left quadrant of its parent.
-func ResizeQuad3(r image.Rectangle) image.Rectangle {
- mid := midpoint(r)
- return image.Rect(r.Min.X, mid.Y, mid.X, r.Max.Y)
-}
-
-// ResizeQuad4 resizes a layout to consume the bottom-right quadrant of its parent.
-func ResizeQuad4(r image.Rectangle) image.Rectangle {
- mid := midpoint(r)
- return image.Rectangle{mid, r.Max}
-}
-
-// midpoint returns the point in the middle of a rectangle.
-func midpoint(r image.Rectangle) image.Point {
- return r.Min.Add(r.Max).Div(2)
-}
diff --git a/layout/resize_test.go b/layout/resize_test.go
deleted file mode 100644
index 5be7a41..0000000
--- a/layout/resize_test.go
+++ /dev/null
@@ -1,58 +0,0 @@
-package layout_test
-
-import (
- "image"
- "testing"
-
- "github.com/faiface/gui/layout"
-)
-
-func TestResizeAll(t *testing.T) {
- t.Parallel()
- parent := image.Rect(111, 222, 333, 444)
- child := layout.ResizeAll(parent)
- want := parent
- if child != want {
- t.Errorf("got %v; want %v", child, want)
- }
-}
-
-func TestResizeQuad1(t *testing.T) {
- t.Parallel()
- parent := image.Rect(111, 222, 333, 444)
- child := layout.ResizeQuad1(parent)
- want := image.Rect(222, 222, 333, 333)
- if child != want {
- t.Errorf("got %v; want %v", child, want)
- }
-}
-
-func TestResizeQuad2(t *testing.T) {
- t.Parallel()
- parent := image.Rect(111, 222, 333, 444)
- child := layout.ResizeQuad2(parent)
- want := image.Rect(111, 222, 222, 333)
- if child != want {
- t.Errorf("got %v; want %v", child, want)
- }
-}
-
-func TestResizeQuad3(t *testing.T) {
- t.Parallel()
- parent := image.Rect(111, 222, 333, 444)
- child := layout.ResizeQuad3(parent)
- want := image.Rect(111, 333, 222, 444)
- if child != want {
- t.Errorf("got %v; want %v", child, want)
- }
-}
-
-func TestResizeQuad4(t *testing.T) {
- t.Parallel()
- parent := image.Rect(111, 222, 333, 444)
- child := layout.ResizeQuad4(parent)
- want := image.Rect(222, 333, 333, 444)
- if child != want {
- t.Errorf("got %v; want %v", child, want)
- }
-}
diff --git a/layout/rows.go b/layout/rows.go
index 603eddc..cfb85c9 100644
--- a/layout/rows.go
+++ b/layout/rows.go
@@ -39,7 +39,7 @@ func NewRows(env gui.Env, nrows uint) []gui.Env {
event := (<-env.Events()).(gui.Resize) // first event guaranteed to be Resize
area := event.Rectangle
rowHeights := make([]uint, nrows) // initially zero until draw call received
- resize(area, rowHeights) // send first Resize to children
+ go resize(area, rowHeights) // send first Resize to children
// Multiplex rows' draw channels. Tag draw functions with row index.
draws := make(chan taggedDrawCall)
@@ -56,9 +56,9 @@ func NewRows(env gui.Env, nrows uint) []gui.Env {
switch event := event.(type) {
case gui.Resize:
area = event.Rectangle
- resize(area, rowHeights)
+ go resize(area, rowHeights)
default:
- multicast(event, eventss) // forward event to all rows
+ go multicast(event, eventss) // forward event to all rows
}
case drw := <-draws: // draw call from a row
rh := rowHeight(area, drw.f)
diff --git a/test/border/main.go b/test/border/main.go
new file mode 100644
index 0000000..315216c
--- /dev/null
+++ b/test/border/main.go
@@ -0,0 +1,95 @@
+package main
+
+import (
+ "image"
+ "image/color"
+ "image/draw"
+ "time"
+
+ "github.com/faiface/gui"
+ "github.com/faiface/gui/layout"
+ "github.com/faiface/gui/win"
+ "github.com/faiface/mainthread"
+)
+
+const (
+ margin = 10
+ border = 2
+ padding = 15
+)
+
+var (
+ bgClr = gui.HexToColor("#999999") // background color
+ brdrClr = color.RGBA{0xFF, 0x00, 0xFF, 0xFF}
+)
+
+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)
+
+ // Background
+ bg := layout.NewRegion(mux.MakeEnv(), bgClr, layout.Full())
+
+ // Margin, border, and padding
+ border := layout.NewBorder(bg, layout.Margin(margin), layout.Border(border, brdrClr), layout.Padding(padding))
+
+ //region := layout.NewRegion(border, color.White, layout.Full())
+ //go func() { for range region.Events() {} }()
+
+ // Region in top-right quadrant
+ region := layout.NewRegion(border, color.Transparent, layout.Quad1())
+ 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 / 6)
+ env.Draw() <- redraw(true)
+ time.Sleep(time.Second / 6)
+ }
+ }()
+ }
+ }
+ }
+ close(env.Draw())
+}
diff --git a/test/region/main.go b/test/region/main.go
index ef7f9ac..0ea358c 100644
--- a/test/region/main.go
+++ b/test/region/main.go
@@ -26,11 +26,11 @@ func run() {
mux, env := gui.NewMux(w)
- // Background region
- bg := layout.NewRegion(mux.MakeEnv(), bgclr, layout.ResizeAll)
+ // Background
+ bg := layout.NewRegion(mux.MakeEnv(), bgclr, layout.Full())
- // Region on top of background, in bottom-right quadrant
- region := layout.NewRegion(bg, color.Transparent, layout.ResizeQuad4)
+ // Region in bottom-right quadrant
+ region := layout.NewRegion(bg, color.Transparent, layout.Quad4())
go blinker(region)
for event := range env.Events() {
diff --git a/test/rows/main.go b/test/rows/main.go
index 29b7344..0b78893 100644
--- a/test/rows/main.go
+++ b/test/rows/main.go
@@ -30,7 +30,7 @@ func run() {
}
mux, env := gui.NewMux(w)
- bg := layout.NewRegion(mux.MakeEnv(), bgclr, layout.ResizeAll)
+ bg := layout.NewRegion(mux.MakeEnv(), bgclr, layout.Full())
rows := layout.NewRows(bg, nrows)
for i, row := range rows {
go colorBlock(row, image.Pt(rowWidth, rowHeight), color.RGBA{uint8(i * 256 / 4), 0x20, 0x20, 0xFF})