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 }