diff options
Diffstat (limited to 'layout/border.go')
| -rw-r--r-- | layout/border.go | 234 |
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 } |