aboutsummaryrefslogtreecommitdiffstats
path: root/layout/border.go
diff options
context:
space:
mode:
Diffstat (limited to 'layout/border.go')
-rw-r--r--layout/border.go234
1 files changed, 234 insertions, 0 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 }