From 3a216b96b6a7c80275a2516e7de82d9b2ffc96df Mon Sep 17 00:00:00 2001 From: Clement Benard Date: Thu, 4 Jul 2019 16:28:51 +0200 Subject: added layout basics --- layout/fixedgrid/fixedgrid.go | 88 ++++++++++++++++++++++ layout/layout.go | 165 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 253 insertions(+) create mode 100644 layout/fixedgrid/fixedgrid.go create mode 100644 layout/layout.go (limited to 'layout') diff --git a/layout/fixedgrid/fixedgrid.go b/layout/fixedgrid/fixedgrid.go new file mode 100644 index 0000000..a9fabf8 --- /dev/null +++ b/layout/fixedgrid/fixedgrid.go @@ -0,0 +1,88 @@ +package fixedgrid + +import ( + "fmt" + "image" + "image/color" + "image/draw" + + "github.com/faiface/gui" + "github.com/faiface/gui/layout" +) + +type FixedGrid struct { + Columns int + Rows int + Background color.Color + Gap int + + *layout.Layout +} + +func New(env gui.Env, options ...func(*FixedGrid)) *FixedGrid { + ret := &FixedGrid{ + // Bounds: image.ZR, + Background: image.Black, + Columns: 1, + Rows: 1, + Gap: 0, + } + + for _, f := range options { + f(ret) + } + + ret.Layout = layout.New(env, ret.layout, ret.redraw) + return ret +} + +func (g *FixedGrid) layout(bounds image.Rectangle) map[string]image.Rectangle { + gap := g.Gap + cols := g.Columns + rows := g.Rows + + w := (bounds.Dx() - (cols+1)*gap) / cols + h := (bounds.Dy() - (rows+1)*gap) / rows + + ret := make(map[string]image.Rectangle) + X := gap + bounds.Min.X + Y := gap + bounds.Min.Y + for x := 0; x < cols; x++ { + for y := 0; y < rows; y++ { + ret[fmt.Sprintf("%d;%d", x, y)] = image.Rect(X, Y, X+w, Y+h) + Y += gap + h + } + Y = gap + bounds.Min.Y + X += gap + w + } + + return ret +} + +func Background(c color.Color) func(*FixedGrid) { + return func(grid *FixedGrid) { + grid.Background = c + } +} + +func Gap(g int) func(*FixedGrid) { + return func(grid *FixedGrid) { + grid.Gap = g + } +} + +func Columns(cols int) func(*FixedGrid) { + return func(grid *FixedGrid) { + grid.Columns = cols + } +} + +func Rows(rows int) func(*FixedGrid) { + return func(grid *FixedGrid) { + grid.Rows = rows + } +} + +func (g *FixedGrid) redraw(drw draw.Image, bounds image.Rectangle) { + draw.Draw(drw, bounds, image.NewUniform(g.Background), image.ZP, draw.Src) +} diff --git a/layout/layout.go b/layout/layout.go new file mode 100644 index 0000000..3601629 --- /dev/null +++ b/layout/layout.go @@ -0,0 +1,165 @@ +package layout + +import ( + "image" + "image/draw" + "sync" + + "github.com/faiface/gui" +) + +type Layout struct { + masterEnv *MuxEnv + inEvent chan<- gui.Event + + mu sync.Mutex + lastResize gui.Event + eventsIns map[string]chan<- gui.Event + draw chan<- func(draw.Image) image.Rectangle + + Lay func(image.Rectangle) map[string]image.Rectangle + Redraw func(draw.Image, image.Rectangle) +} + +func New( + env gui.Env, + lay func(image.Rectangle) map[string]image.Rectangle, + redraw func(draw.Image, image.Rectangle), +) *Layout { + + mux := &Layout{ + Lay: lay, + Redraw: redraw, + } + drawChan := make(chan func(draw.Image) image.Rectangle) + mux.draw = drawChan + mux.masterEnv = mux.makeEnv("master", true) + mux.inEvent = mux.masterEnv.In + mux.eventsIns = make(map[string]chan<- gui.Event) + go func() { + for d := range drawChan { + env.Draw() <- d + } + close(env.Draw()) + }() + + go func() { + for e := range env.Events() { + mux.inEvent <- e + } + }() + + go func() { + for e := range mux.masterEnv.Events() { + mux.mu.Lock() + if resize, ok := e.(gui.Resize); ok { + mux.lastResize = resize + rect := resize.Rectangle + + mux.draw <- func(drw draw.Image) image.Rectangle { + mux.Redraw(drw, rect) + return rect + } + l := mux.Lay(rect) + + for key, eventsIn := range mux.eventsIns { + func(rz gui.Resize) { + rz.Rectangle = l[key] + eventsIn <- rz + }(resize) + } + } else { + for _, eventsIn := range mux.eventsIns { + eventsIn <- e + } + } + mux.mu.Unlock() + } + mux.mu.Lock() + for _, eventsIn := range mux.eventsIns { + close(eventsIn) + } + mux.mu.Unlock() + }() + + return mux +} + +func (mux *Layout) GetEnv(name string) gui.Env { + return mux.makeEnv(name, false) +} + +type MuxEnv struct { + In chan<- gui.Event + events <-chan gui.Event + draw chan<- func(draw.Image) image.Rectangle +} + +func (m *MuxEnv) Events() <-chan gui.Event { return m.events } +func (m *MuxEnv) Draw() chan<- func(draw.Image) image.Rectangle { return m.draw } + +// We do not store master env +func (mux *Layout) makeEnv(envName string, master bool) *MuxEnv { + eventsOut, eventsIn := gui.MakeEventsChan() + drawChan := make(chan func(draw.Image) image.Rectangle) + env := &MuxEnv{eventsIn, eventsOut, drawChan} + + mux.mu.Lock() + if !master { + mux.eventsIns[envName] = eventsIn + } + + // make sure to always send a resize event to a new Env if we got the size already + // that means it missed the resize event by the root Env + if mux.lastResize != nil { + eventsIn <- mux.lastResize + } + mux.mu.Unlock() + + go func() { + func() { + // When the master Env gets its Draw() channel closed, it closes all the Events() + // channels of all the children Envs, and it also closes the internal draw channel + // of the Mux. Otherwise, closing the Draw() channel of the master Env wouldn't + // close the Env the Mux is muxing. However, some child Envs of the Mux may still + // send some drawing commmands before they realize that their Events() channel got + // closed. + // + // That is perfectly fine if their drawing commands simply get ignored. This down here + // is a little hacky, but (I hope) perfectly fine solution to the problem. + // + // When the internal draw channel of the Mux gets closed, the line marked with ! will + // cause panic. We recover this panic, then we receive, but ignore all furhter draw + // commands, correctly draining the Env until it closes itself. + defer func() { + if recover() != nil { + for range drawChan { + } + } + }() + for d := range drawChan { + mux.draw <- d // ! + } + }() + if master { + mux.mu.Lock() + for _, eventsIn := range mux.eventsIns { + close(eventsIn) + } + mux.eventsIns = nil + close(mux.draw) + mux.mu.Unlock() + } else { + mux.mu.Lock() + delete(mux.eventsIns, envName) + + close(eventsIn) + mux.mu.Unlock() + } + if mux.lastResize != nil { + mux.inEvent <- mux.lastResize + } + }() + + return env +} -- cgit v1.2.3 From 1223e277009005337243ca991cb54dd75bf723a7 Mon Sep 17 00:00:00 2001 From: Clement Benard Date: Tue, 9 Jul 2019 10:59:38 +0200 Subject: Layout system remaking --- layout/box.go | 99 +++++++++++++++++++++++++ layout/fixedgrid/fixedgrid.go | 88 ---------------------- layout/grid.go | 92 +++++++++++++++++++++++ layout/layout.go | 168 +++--------------------------------------- layout/mux.go | 150 +++++++++++++++++++++++++++++++++++++ 5 files changed, 352 insertions(+), 245 deletions(-) create mode 100644 layout/box.go delete mode 100644 layout/fixedgrid/fixedgrid.go create mode 100644 layout/grid.go create mode 100644 layout/mux.go (limited to 'layout') diff --git a/layout/box.go b/layout/box.go new file mode 100644 index 0000000..568ac58 --- /dev/null +++ b/layout/box.go @@ -0,0 +1,99 @@ +package layout + +import ( + "image" + "image/color" + "image/draw" + "log" + + "github.com/faiface/gui" +) + +func evenSplit(elements int, width int) []int { + ret := make([]int, 0, elements) + for elements > 0 { + v := width / elements + width -= v + elements -= 1 + ret = append(ret, v) + } + return ret +} + +type Box struct { + // Defaults to []*gui.Env{} + Contents []*gui.Env + // Defaults to image.Black + Background color.Color + // Defaults to an even split + Split func(int, int) []int + // Defaults to 0 + Gap int + + vertical bool +} + +func NewBox(env gui.Env, contents []*gui.Env, options ...func(*Box)) { + ret := &Box{ + Background: image.Black, + Contents: contents, + Split: evenSplit, + } + for _, f := range options { + f(ret) + } + + mux := NewMux(env, ret) + for _, item := range contents { + *item, _ = mux.makeEnv(false) + } +} + +func BoxVertical(b *Box) { + b.vertical = true +} + +func BoxBackground(c color.Color) func(*Box) { + return func(grid *Box) { + grid.Background = c + } +} + +func BoxSplit(split func(int, int) []int) func(*Box) { + return func(grid *Box) { + grid.Split = split + } +} + +func BoxGap(gap int) func(*Box) { + return func(grid *Box) { + grid.Gap = gap + } +} + +func (g *Box) Redraw(drw draw.Image, bounds image.Rectangle) { + draw.Draw(drw, bounds, image.NewUniform(g.Background), image.ZP, draw.Src) +} + +func (g *Box) Lay(bounds image.Rectangle) []image.Rectangle { + items := len(g.Contents) + ret := make([]image.Rectangle, 0, items) + if g.vertical { + spl := g.Split(items, bounds.Dy()-(g.Gap*(items+1))) + Y := bounds.Min.Y + g.Gap + for _, item := range spl { + ret = append(ret, image.Rect(bounds.Min.X+g.Gap, Y, bounds.Max.X-g.Gap, Y+item)) + Y += item + g.Gap + } + } else { + spl := g.Split(items, bounds.Dx()-(g.Gap*(items+1))) + X := bounds.Min.X + g.Gap + for _, item := range spl { + ret = append(ret, image.Rect(X, bounds.Min.Y+g.Gap, X+item, bounds.Max.Y-g.Gap)) + X += item + g.Gap + } + } + + log.Print(ret) + return ret +} diff --git a/layout/fixedgrid/fixedgrid.go b/layout/fixedgrid/fixedgrid.go deleted file mode 100644 index a9fabf8..0000000 --- a/layout/fixedgrid/fixedgrid.go +++ /dev/null @@ -1,88 +0,0 @@ -package fixedgrid - -import ( - "fmt" - "image" - "image/color" - "image/draw" - - "github.com/faiface/gui" - "github.com/faiface/gui/layout" -) - -type FixedGrid struct { - Columns int - Rows int - Background color.Color - Gap int - - *layout.Layout -} - -func New(env gui.Env, options ...func(*FixedGrid)) *FixedGrid { - ret := &FixedGrid{ - // Bounds: image.ZR, - Background: image.Black, - Columns: 1, - Rows: 1, - Gap: 0, - } - - for _, f := range options { - f(ret) - } - - ret.Layout = layout.New(env, ret.layout, ret.redraw) - return ret -} - -func (g *FixedGrid) layout(bounds image.Rectangle) map[string]image.Rectangle { - gap := g.Gap - cols := g.Columns - rows := g.Rows - - w := (bounds.Dx() - (cols+1)*gap) / cols - h := (bounds.Dy() - (rows+1)*gap) / rows - - ret := make(map[string]image.Rectangle) - X := gap + bounds.Min.X - Y := gap + bounds.Min.Y - for x := 0; x < cols; x++ { - for y := 0; y < rows; y++ { - ret[fmt.Sprintf("%d;%d", x, y)] = image.Rect(X, Y, X+w, Y+h) - Y += gap + h - } - Y = gap + bounds.Min.Y - X += gap + w - } - - return ret -} - -func Background(c color.Color) func(*FixedGrid) { - return func(grid *FixedGrid) { - grid.Background = c - } -} - -func Gap(g int) func(*FixedGrid) { - return func(grid *FixedGrid) { - grid.Gap = g - } -} - -func Columns(cols int) func(*FixedGrid) { - return func(grid *FixedGrid) { - grid.Columns = cols - } -} - -func Rows(rows int) func(*FixedGrid) { - return func(grid *FixedGrid) { - grid.Rows = rows - } -} - -func (g *FixedGrid) redraw(drw draw.Image, bounds image.Rectangle) { - draw.Draw(drw, bounds, image.NewUniform(g.Background), image.ZP, draw.Src) -} diff --git a/layout/grid.go b/layout/grid.go new file mode 100644 index 0000000..55111cb --- /dev/null +++ b/layout/grid.go @@ -0,0 +1,92 @@ +package layout + +import ( + "image" + "image/color" + "image/draw" + + "github.com/faiface/gui" +) + +// Grid represents a simple grid layout. +// Do not edit properties directly, use the constructor instead. +type Grid struct { + Contents [][]*gui.Env + Background color.Color + Gap int + SplitX func(int, int) []int + SplitY func(int, int) []int +} + +func NewGrid(env gui.Env, contents [][]*gui.Env, options ...func(*Grid)) { + ret := &Grid{ + Background: image.Black, + Gap: 0, + Contents: contents, + SplitX: evenSplit, + SplitY: evenSplit, + } + for _, f := range options { + f(ret) + } + + mux := NewMux(env, ret) + for _, row := range contents { + for _, item := range row { + *item, _ = mux.makeEnv(false) + } + } +} + +func GridBackground(c color.Color) func(*Grid) { + return func(grid *Grid) { + grid.Background = c + } +} + +func GridGap(g int) func(*Grid) { + return func(grid *Grid) { + grid.Gap = g + } +} + +func GridSplitX(split func(int, int) []int) func(*Grid) { + return func(grid *Grid) { + grid.SplitX = split + } +} + +func GridSplitY(split func(int, int) []int) func(*Grid) { + return func(grid *Grid) { + grid.SplitY = split + } +} + +func (g *Grid) Redraw(drw draw.Image, bounds image.Rectangle) { + draw.Draw(drw, bounds, image.NewUniform(g.Background), image.ZP, draw.Src) +} + +func (g *Grid) Lay(bounds image.Rectangle) []image.Rectangle { + gap := g.Gap + ret := make([]image.Rectangle, 0) + rows := len(g.Contents) + + rowsH := g.SplitY(rows, bounds.Dy()-(g.Gap*(rows+1))) + + X := gap + bounds.Min.X + Y := gap + bounds.Min.Y + for y, row := range g.Contents { + cols := len(row) + h := rowsH[y] + colsW := g.SplitX(cols, bounds.Dx()-(g.Gap*(cols+1))) + X = gap + bounds.Min.X + for x := range row { + w := colsW[x] + ret = append(ret, image.Rect(X, Y, X+w, Y+h)) + X += gap + w + } + Y += gap + h + } + + return ret +} diff --git a/layout/layout.go b/layout/layout.go index 3601629..987ee24 100644 --- a/layout/layout.go +++ b/layout/layout.go @@ -3,163 +3,17 @@ package layout import ( "image" "image/draw" - "sync" - - "github.com/faiface/gui" ) -type Layout struct { - masterEnv *MuxEnv - inEvent chan<- gui.Event - - mu sync.Mutex - lastResize gui.Event - eventsIns map[string]chan<- gui.Event - draw chan<- func(draw.Image) image.Rectangle - - Lay func(image.Rectangle) map[string]image.Rectangle - Redraw func(draw.Image, image.Rectangle) -} - -func New( - env gui.Env, - lay func(image.Rectangle) map[string]image.Rectangle, - redraw func(draw.Image, image.Rectangle), -) *Layout { - - mux := &Layout{ - Lay: lay, - Redraw: redraw, - } - drawChan := make(chan func(draw.Image) image.Rectangle) - mux.draw = drawChan - mux.masterEnv = mux.makeEnv("master", true) - mux.inEvent = mux.masterEnv.In - mux.eventsIns = make(map[string]chan<- gui.Event) - go func() { - for d := range drawChan { - env.Draw() <- d - } - close(env.Draw()) - }() - - go func() { - for e := range env.Events() { - mux.inEvent <- e - } - }() - - go func() { - for e := range mux.masterEnv.Events() { - mux.mu.Lock() - if resize, ok := e.(gui.Resize); ok { - mux.lastResize = resize - rect := resize.Rectangle - - mux.draw <- func(drw draw.Image) image.Rectangle { - mux.Redraw(drw, rect) - return rect - } - l := mux.Lay(rect) - - for key, eventsIn := range mux.eventsIns { - func(rz gui.Resize) { - rz.Rectangle = l[key] - eventsIn <- rz - }(resize) - } - } else { - for _, eventsIn := range mux.eventsIns { - eventsIn <- e - } - } - mux.mu.Unlock() - } - mux.mu.Lock() - for _, eventsIn := range mux.eventsIns { - close(eventsIn) - } - mux.mu.Unlock() - }() - - return mux -} - -func (mux *Layout) GetEnv(name string) gui.Env { - return mux.makeEnv(name, false) -} - -type MuxEnv struct { - In chan<- gui.Event - events <-chan gui.Event - draw chan<- func(draw.Image) image.Rectangle -} - -func (m *MuxEnv) Events() <-chan gui.Event { return m.events } -func (m *MuxEnv) Draw() chan<- func(draw.Image) image.Rectangle { return m.draw } - -// We do not store master env -func (mux *Layout) makeEnv(envName string, master bool) *MuxEnv { - eventsOut, eventsIn := gui.MakeEventsChan() - drawChan := make(chan func(draw.Image) image.Rectangle) - env := &MuxEnv{eventsIn, eventsOut, drawChan} - - mux.mu.Lock() - if !master { - mux.eventsIns[envName] = eventsIn - } - - // make sure to always send a resize event to a new Env if we got the size already - // that means it missed the resize event by the root Env - if mux.lastResize != nil { - eventsIn <- mux.lastResize - } - mux.mu.Unlock() - - go func() { - func() { - // When the master Env gets its Draw() channel closed, it closes all the Events() - // channels of all the children Envs, and it also closes the internal draw channel - // of the Mux. Otherwise, closing the Draw() channel of the master Env wouldn't - // close the Env the Mux is muxing. However, some child Envs of the Mux may still - // send some drawing commmands before they realize that their Events() channel got - // closed. - // - // That is perfectly fine if their drawing commands simply get ignored. This down here - // is a little hacky, but (I hope) perfectly fine solution to the problem. - // - // When the internal draw channel of the Mux gets closed, the line marked with ! will - // cause panic. We recover this panic, then we receive, but ignore all furhter draw - // commands, correctly draining the Env until it closes itself. - defer func() { - if recover() != nil { - for range drawChan { - } - } - }() - for d := range drawChan { - mux.draw <- d // ! - } - }() - if master { - mux.mu.Lock() - for _, eventsIn := range mux.eventsIns { - close(eventsIn) - } - mux.eventsIns = nil - close(mux.draw) - mux.mu.Unlock() - } else { - mux.mu.Lock() - delete(mux.eventsIns, envName) - - close(eventsIn) - mux.mu.Unlock() - } - if mux.lastResize != nil { - mux.inEvent <- mux.lastResize - } - }() - - return env +// Layout represents any graphical layout +// +// A Layout needs to be able to redraw itself with the Redraw method. +// Redraw() only draws the background or frame of the Layout, not the childs. +// +// Lay represents the way to divide space among your childs. +// It takes a parameter of how much space is available, +// and returns where exactly to put its childs. +type Layout interface { + Lay(image.Rectangle) []image.Rectangle + Redraw(draw.Image, image.Rectangle) } diff --git a/layout/mux.go b/layout/mux.go new file mode 100644 index 0000000..abae087 --- /dev/null +++ b/layout/mux.go @@ -0,0 +1,150 @@ +package layout + +import ( + "image" + "image/draw" + "sync" + + "github.com/faiface/gui" +) + +type Mux struct { + masterEnv *muxEnv + inEvent chan<- gui.Event + + mu sync.Mutex + lastResize gui.Event + eventsIns []chan<- gui.Event + draw chan<- func(draw.Image) image.Rectangle + + Layout +} + +func NewMux(env gui.Env, l Layout) (mux *Mux) { + drawChan := make(chan func(draw.Image) image.Rectangle) + mux = &Mux{ + Layout: l, + draw: drawChan, + } + mux.masterEnv, mux.inEvent = mux.makeEnv(true) + mux.eventsIns = make([]chan<- gui.Event, 0) + + go func() { + for d := range drawChan { + env.Draw() <- d + } + close(env.Draw()) + }() + + go func() { + for e := range env.Events() { + mux.mu.Lock() + if resize, ok := e.(gui.Resize); ok { + mux.lastResize = resize + rect := resize.Rectangle + + // Redraw self + mux.draw <- func(drw draw.Image) image.Rectangle { + mux.Redraw(drw, rect) + return rect + } + + // Send appropriate resize Events to childs + lay := mux.Lay(rect) + for i, eventsIn := range mux.eventsIns { + resize.Rectangle = lay[i] + eventsIn <- resize + } + } else { + for _, eventsIn := range mux.eventsIns { + eventsIn <- e + } + } + mux.mu.Unlock() + } + mux.mu.Lock() + for _, eventsIn := range mux.eventsIns { + close(eventsIn) + } + mux.mu.Unlock() + }() + + return mux +} + +type muxEnv struct { + events <-chan gui.Event + draw chan<- func(draw.Image) image.Rectangle +} + +func (m *muxEnv) Events() <-chan gui.Event { return m.events } +func (m *muxEnv) Draw() chan<- func(draw.Image) image.Rectangle { return m.draw } + +// We do not store master env +func (mux *Mux) makeEnv(master bool) (*muxEnv, chan<- gui.Event) { + eventsOut, eventsIn := gui.MakeEventsChan() + drawChan := make(chan func(draw.Image) image.Rectangle) + env := &muxEnv{eventsOut, drawChan} + + mux.mu.Lock() + mux.eventsIns = append(mux.eventsIns, eventsIn) + // make sure to always send a resize event to a new Env if we got the size already + // that means it missed the resize event by the root Env + if mux.lastResize != nil { + eventsIn <- mux.lastResize + } + mux.mu.Unlock() + + go func() { + func() { + // When the master Env gets its Draw() channel closed, it closes all the Events() + // channels of all the children Envs, and it also closes the internal draw channel + // of the Mux. Otherwise, closing the Draw() channel of the master Env wouldn't + // close the Env the Mux is muxing. However, some child Envs of the Mux may still + // send some drawing commmands before they realize that their Events() channel got + // closed. + // + // That is perfectly fine if their drawing commands simply get ignored. This down here + // is a little hacky, but (I hope) perfectly fine solution to the problem. + // + // When the internal draw channel of the Mux gets closed, the line marked with ! will + // cause panic. We recover this panic, then we receive, but ignore all furhter draw + // commands, correctly draining the Env until it closes itself. + defer func() { + if recover() != nil { + for range drawChan { + } + } + }() + for d := range drawChan { + mux.draw <- d // ! + } + }() + if master { + mux.mu.Lock() + for _, eventsIn := range mux.eventsIns { + close(eventsIn) + } + mux.eventsIns = nil + close(mux.draw) + mux.mu.Unlock() + } else { + mux.mu.Lock() + i := -1 + for i = range mux.eventsIns { + if mux.eventsIns[i] == eventsIn { + break + } + } + if i != -1 { + mux.eventsIns = append(mux.eventsIns[:i], mux.eventsIns[i+1:]...) + } + mux.mu.Unlock() + } + if mux.lastResize != nil { + mux.inEvent <- mux.lastResize + } + }() + + return env, eventsIn +} -- cgit v1.2.3 From 1415586e633ee33194442f131a5a691f889c8ee5 Mon Sep 17 00:00:00 2001 From: Clement Benard Date: Tue, 9 Jul 2019 15:31:32 +0200 Subject: better Mux and Env handling in layout --- layout/box.go | 7 ++++--- layout/grid.go | 8 +++++--- layout/mux.go | 40 ++++++++++++++++++++++++---------------- 3 files changed, 33 insertions(+), 22 deletions(-) (limited to 'layout') diff --git a/layout/box.go b/layout/box.go index 568ac58..4e16a76 100644 --- a/layout/box.go +++ b/layout/box.go @@ -33,7 +33,7 @@ type Box struct { vertical bool } -func NewBox(env gui.Env, contents []*gui.Env, options ...func(*Box)) { +func NewBox(env gui.Env, contents []*gui.Env, options ...func(*Box)) gui.Env { ret := &Box{ Background: image.Black, Contents: contents, @@ -43,10 +43,11 @@ func NewBox(env gui.Env, contents []*gui.Env, options ...func(*Box)) { f(ret) } - mux := NewMux(env, ret) + mux, env := NewMux(env, ret) for _, item := range contents { - *item, _ = mux.makeEnv(false) + *item = mux.MakeEnv() } + return env } func BoxVertical(b *Box) { diff --git a/layout/grid.go b/layout/grid.go index 55111cb..1831794 100644 --- a/layout/grid.go +++ b/layout/grid.go @@ -18,7 +18,7 @@ type Grid struct { SplitY func(int, int) []int } -func NewGrid(env gui.Env, contents [][]*gui.Env, options ...func(*Grid)) { +func NewGrid(env gui.Env, contents [][]*gui.Env, options ...func(*Grid)) gui.Env { ret := &Grid{ Background: image.Black, Gap: 0, @@ -30,12 +30,14 @@ func NewGrid(env gui.Env, contents [][]*gui.Env, options ...func(*Grid)) { f(ret) } - mux := NewMux(env, ret) + mux, env := NewMux(env, ret) for _, row := range contents { for _, item := range row { - *item, _ = mux.makeEnv(false) + *item = mux.MakeEnv() } } + + return env } func GridBackground(c color.Color) func(*Grid) { diff --git a/layout/mux.go b/layout/mux.go index abae087..df499df 100644 --- a/layout/mux.go +++ b/layout/mux.go @@ -9,8 +9,7 @@ import ( ) type Mux struct { - masterEnv *muxEnv - inEvent chan<- gui.Event + inEvent chan<- gui.Event mu sync.Mutex lastResize gui.Event @@ -20,13 +19,15 @@ type Mux struct { Layout } -func NewMux(env gui.Env, l Layout) (mux *Mux) { +func (m *Mux) InEvent() chan<- gui.Event { return m.inEvent } + +func NewMux(env gui.Env, l Layout) (mux *Mux, master gui.Env) { drawChan := make(chan func(draw.Image) image.Rectangle) mux = &Mux{ Layout: l, draw: drawChan, } - mux.masterEnv, mux.inEvent = mux.makeEnv(true) + master, mux.inEvent = mux.makeEnv(true) mux.eventsIns = make([]chan<- gui.Event, 0) go func() { @@ -68,8 +69,7 @@ func NewMux(env gui.Env, l Layout) (mux *Mux) { } mux.mu.Unlock() }() - - return mux + return } type muxEnv struct { @@ -80,20 +80,27 @@ type muxEnv struct { func (m *muxEnv) Events() <-chan gui.Event { return m.events } func (m *muxEnv) Draw() chan<- func(draw.Image) image.Rectangle { return m.draw } +func (mux *Mux) MakeEnv() gui.Env { + env, _ := mux.makeEnv(false) + return env +} + // We do not store master env func (mux *Mux) makeEnv(master bool) (*muxEnv, chan<- gui.Event) { eventsOut, eventsIn := gui.MakeEventsChan() drawChan := make(chan func(draw.Image) image.Rectangle) env := &muxEnv{eventsOut, drawChan} - mux.mu.Lock() - mux.eventsIns = append(mux.eventsIns, eventsIn) - // make sure to always send a resize event to a new Env if we got the size already - // that means it missed the resize event by the root Env - if mux.lastResize != nil { - eventsIn <- mux.lastResize + if !master { + mux.mu.Lock() + mux.eventsIns = append(mux.eventsIns, eventsIn) + // make sure to always send a resize event to a new Env if we got the size already + // that means it missed the resize event by the root Env + if mux.lastResize != nil { + eventsIn <- mux.lastResize + } + mux.mu.Unlock() } - mux.mu.Unlock() go func() { func() { @@ -126,6 +133,7 @@ func (mux *Mux) makeEnv(master bool) (*muxEnv, chan<- gui.Event) { close(eventsIn) } mux.eventsIns = nil + close(mux.inEvent) close(mux.draw) mux.mu.Unlock() } else { @@ -139,11 +147,11 @@ func (mux *Mux) makeEnv(master bool) (*muxEnv, chan<- gui.Event) { if i != -1 { mux.eventsIns = append(mux.eventsIns[:i], mux.eventsIns[i+1:]...) } + if mux.lastResize != nil { + mux.InEvent() <- mux.lastResize + } mux.mu.Unlock() } - if mux.lastResize != nil { - mux.inEvent <- mux.lastResize - } }() return env, eventsIn -- cgit v1.2.3 From 24286694a5ba80cc3f54a224b62ac11c773b4985 Mon Sep 17 00:00:00 2001 From: Clement Benard Date: Tue, 9 Jul 2019 17:30:30 +0200 Subject: Documenting, bug fixing and refactoring to be more in line with the project. --- layout/box.go | 3 --- layout/mux.go | 57 +++++++++++++++++++++++++++++++++++---------------------- 2 files changed, 35 insertions(+), 25 deletions(-) (limited to 'layout') diff --git a/layout/box.go b/layout/box.go index 4e16a76..2fd29cf 100644 --- a/layout/box.go +++ b/layout/box.go @@ -4,7 +4,6 @@ import ( "image" "image/color" "image/draw" - "log" "github.com/faiface/gui" ) @@ -94,7 +93,5 @@ func (g *Box) Lay(bounds image.Rectangle) []image.Rectangle { X += item + g.Gap } } - - log.Print(ret) return ret } diff --git a/layout/mux.go b/layout/mux.go index df499df..208664c 100644 --- a/layout/mux.go +++ b/layout/mux.go @@ -8,28 +8,34 @@ import ( "github.com/faiface/gui" ) +// Mux can be used to multiplex an Env, let's call it a root Env. Mux implements a way to +// create multiple virtual Envs that all interact with the root Env. They receive the same +// events apart from gui.Resize, and their draw functions get redirected to the root Env. +// +// All gui.Resize events are instead modified according to the underlying Layout. +// The master Env gets the original gui.Resize events. type Mux struct { - inEvent chan<- gui.Event - mu sync.Mutex lastResize gui.Event eventsIns []chan<- gui.Event draw chan<- func(draw.Image) image.Rectangle - Layout + evIn chan<- gui.Event + layout Layout } -func (m *Mux) InEvent() chan<- gui.Event { return m.inEvent } - +// NewMux should only be used internally by Layouts. +// It has mostly the same behaviour as gui.Mux, except for its use of and underlying Layout +// for modifying the gui.Resize events. to the childs. func NewMux(env gui.Env, l Layout) (mux *Mux, master gui.Env) { drawChan := make(chan func(draw.Image) image.Rectangle) mux = &Mux{ - Layout: l, + layout: l, draw: drawChan, } - master, mux.inEvent = mux.makeEnv(true) - mux.eventsIns = make([]chan<- gui.Event, 0) - + master, masterIn := mux.makeEnv(true) + events := make(chan gui.Event, 0) + mux.evIn = events go func() { for d := range drawChan { env.Draw() <- d @@ -39,23 +45,33 @@ func NewMux(env gui.Env, l Layout) (mux *Mux, master gui.Env) { go func() { for e := range env.Events() { + events <- e + } + }() + + go func() { + for e := range events { + // master gets a copy of all events to the Mux + masterIn <- e mux.mu.Lock() if resize, ok := e.(gui.Resize); ok { mux.lastResize = resize + rect := resize.Rectangle // Redraw self mux.draw <- func(drw draw.Image) image.Rectangle { - mux.Redraw(drw, rect) + mux.layout.Redraw(drw, rect) return rect } // Send appropriate resize Events to childs - lay := mux.Lay(rect) + lay := mux.layout.Lay(rect) for i, eventsIn := range mux.eventsIns { resize.Rectangle = lay[i] eventsIn <- resize } + } else { for _, eventsIn := range mux.eventsIns { eventsIn <- e @@ -72,6 +88,11 @@ func NewMux(env gui.Env, l Layout) (mux *Mux, master gui.Env) { return } +func (mux *Mux) MakeEnv() gui.Env { + env, _ := mux.makeEnv(false) + return env +} + type muxEnv struct { events <-chan gui.Event draw chan<- func(draw.Image) image.Rectangle @@ -80,16 +101,11 @@ type muxEnv struct { func (m *muxEnv) Events() <-chan gui.Event { return m.events } func (m *muxEnv) Draw() chan<- func(draw.Image) image.Rectangle { return m.draw } -func (mux *Mux) MakeEnv() gui.Env { - env, _ := mux.makeEnv(false) - return env -} - // We do not store master env -func (mux *Mux) makeEnv(master bool) (*muxEnv, chan<- gui.Event) { +func (mux *Mux) makeEnv(master bool) (env gui.Env, eventsIn chan<- gui.Event) { eventsOut, eventsIn := gui.MakeEventsChan() drawChan := make(chan func(draw.Image) image.Rectangle) - env := &muxEnv{eventsOut, drawChan} + env = &muxEnv{eventsOut, drawChan} if !master { mux.mu.Lock() @@ -133,7 +149,6 @@ func (mux *Mux) makeEnv(master bool) (*muxEnv, chan<- gui.Event) { close(eventsIn) } mux.eventsIns = nil - close(mux.inEvent) close(mux.draw) mux.mu.Unlock() } else { @@ -147,10 +162,8 @@ func (mux *Mux) makeEnv(master bool) (*muxEnv, chan<- gui.Event) { if i != -1 { mux.eventsIns = append(mux.eventsIns[:i], mux.eventsIns[i+1:]...) } - if mux.lastResize != nil { - mux.InEvent() <- mux.lastResize - } mux.mu.Unlock() + mux.evIn <- mux.lastResize } }() -- cgit v1.2.3 From 009f865bc5484e54f09d2173b2b3dbbf3838d391 Mon Sep 17 00:00:00 2001 From: Clement Benard Date: Thu, 11 Jul 2019 10:50:37 +0200 Subject: Added SplitFunc type, documented everything exported --- layout/box.go | 54 +++++++++++++++++++++++------------------------------- layout/grid.go | 43 ++++++++++++++++++++++++------------------- layout/split.go | 19 +++++++++++++++++++ 3 files changed, 66 insertions(+), 50 deletions(-) create mode 100644 layout/split.go (limited to 'layout') diff --git a/layout/box.go b/layout/box.go index 2fd29cf..01a1143 100644 --- a/layout/box.go +++ b/layout/box.go @@ -8,35 +8,22 @@ import ( "github.com/faiface/gui" ) -func evenSplit(elements int, width int) []int { - ret := make([]int, 0, elements) - for elements > 0 { - v := width / elements - width -= v - elements -= 1 - ret = append(ret, v) - } - return ret -} - -type Box struct { - // Defaults to []*gui.Env{} - Contents []*gui.Env - // Defaults to image.Black +type box struct { + Contents []*gui.Env Background color.Color - // Defaults to an even split - Split func(int, int) []int - // Defaults to 0 - Gap int + Split SplitFunc + Gap int vertical bool } -func NewBox(env gui.Env, contents []*gui.Env, options ...func(*Box)) gui.Env { - ret := &Box{ +// NewBox creates a familiar flexbox-like list layout. +// It can be horizontal or vertical. +func NewBox(env gui.Env, contents []*gui.Env, options ...func(*box)) gui.Env { + ret := &box{ Background: image.Black, Contents: contents, - Split: evenSplit, + Split: EvenSplit, } for _, f := range options { f(ret) @@ -49,33 +36,38 @@ func NewBox(env gui.Env, contents []*gui.Env, options ...func(*Box)) gui.Env { return env } -func BoxVertical(b *Box) { +// BoxVertical changes the otherwise horizontal Box to be vertical. +func BoxVertical(b *box) { b.vertical = true } -func BoxBackground(c color.Color) func(*Box) { - return func(grid *Box) { +// BoxBackground changes the background of the box to a uniform color. +func BoxBackground(c color.Color) func(*box) { + return func(grid *box) { grid.Background = c } } -func BoxSplit(split func(int, int) []int) func(*Box) { - return func(grid *Box) { +// BoxSplit changes the way the space is divided among the elements. +func BoxSplit(split SplitFunc) func(*box) { + return func(grid *box) { grid.Split = split } } -func BoxGap(gap int) func(*Box) { - return func(grid *Box) { +// BoxGap changes the box gap. +// The gap is identical everywhere (top, left, bottom, right). +func BoxGap(gap int) func(*box) { + return func(grid *box) { grid.Gap = gap } } -func (g *Box) Redraw(drw draw.Image, bounds image.Rectangle) { +func (g *box) Redraw(drw draw.Image, bounds image.Rectangle) { draw.Draw(drw, bounds, image.NewUniform(g.Background), image.ZP, draw.Src) } -func (g *Box) Lay(bounds image.Rectangle) []image.Rectangle { +func (g *box) Lay(bounds image.Rectangle) []image.Rectangle { items := len(g.Contents) ret := make([]image.Rectangle, 0, items) if g.vertical { diff --git a/layout/grid.go b/layout/grid.go index 1831794..9c79343 100644 --- a/layout/grid.go +++ b/layout/grid.go @@ -8,23 +8,23 @@ import ( "github.com/faiface/gui" ) -// Grid represents a simple grid layout. -// Do not edit properties directly, use the constructor instead. -type Grid struct { +type grid struct { Contents [][]*gui.Env Background color.Color Gap int - SplitX func(int, int) []int - SplitY func(int, int) []int + SplitX SplitFunc + SplitY SplitFunc } -func NewGrid(env gui.Env, contents [][]*gui.Env, options ...func(*Grid)) gui.Env { - ret := &Grid{ +// NewGrid creates a familiar flexbox-like grid layout. +// Each row can be a different length. +func NewGrid(env gui.Env, contents [][]*gui.Env, options ...func(*grid)) gui.Env { + ret := &grid{ Background: image.Black, Gap: 0, Contents: contents, - SplitX: evenSplit, - SplitY: evenSplit, + SplitX: EvenSplit, + SplitY: EvenSplit, } for _, f := range options { f(ret) @@ -40,35 +40,40 @@ func NewGrid(env gui.Env, contents [][]*gui.Env, options ...func(*Grid)) gui.Env return env } -func GridBackground(c color.Color) func(*Grid) { - return func(grid *Grid) { +// GridBackground changes the background of the grid to a uniform color. +func GridBackground(c color.Color) func(*grid) { + return func(grid *grid) { grid.Background = c } } -func GridGap(g int) func(*Grid) { - return func(grid *Grid) { +// GridGap changes the grid gap. +// The gap is identical everywhere (top, left, bottom, right). +func GridGap(g int) func(*grid) { + return func(grid *grid) { grid.Gap = g } } -func GridSplitX(split func(int, int) []int) func(*Grid) { - return func(grid *Grid) { +// GridSplitX changes the way the space is divided among the columns in each row. +func GridSplitX(split SplitFunc) func(*grid) { + return func(grid *grid) { grid.SplitX = split } } -func GridSplitY(split func(int, int) []int) func(*Grid) { - return func(grid *Grid) { +// GridSplitY changes the way the space is divided among the rows. +func GridSplitY(split SplitFunc) func(*grid) { + return func(grid *grid) { grid.SplitY = split } } -func (g *Grid) Redraw(drw draw.Image, bounds image.Rectangle) { +func (g *grid) Redraw(drw draw.Image, bounds image.Rectangle) { draw.Draw(drw, bounds, image.NewUniform(g.Background), image.ZP, draw.Src) } -func (g *Grid) Lay(bounds image.Rectangle) []image.Rectangle { +func (g *grid) Lay(bounds image.Rectangle) []image.Rectangle { gap := g.Gap ret := make([]image.Rectangle, 0) rows := len(g.Contents) diff --git a/layout/split.go b/layout/split.go new file mode 100644 index 0000000..4bf00a7 --- /dev/null +++ b/layout/split.go @@ -0,0 +1,19 @@ +package layout + +// SplitFunc represents a way to split a space among a number of elements. +// The space of the returned slice must be equal to the number of elements. +// The sum of all elements of the returned slice must be eqal to the space. +type SplitFunc func(elements int, space int) []int + +// EvenSplit implements SplitFunc to split a space (almost) evenly among the elements. +// It is almost evenly because width may not be divisible by elements. +func EvenSplit(elements int, width int) []int { + ret := make([]int, 0, elements) + for elements > 0 { + v := width / elements + width -= v + elements -= 1 + ret = append(ret, v) + } + return ret +} -- cgit v1.2.3 From 0cc6367a881ab7dba48ace7b111e0eb1151c10bb Mon Sep 17 00:00:00 2001 From: Clement Benard Date: Fri, 12 Jul 2019 17:23:28 +0200 Subject: Better separation between Layout and Mux --- layout/box.go | 13 ++++++------- layout/grid.go | 21 +++++++++++---------- layout/layout.go | 9 +++++++-- layout/mux.go | 43 +++++++++++++++++++++++++++++++++++++------ layout/split.go | 9 +++++++-- 5 files changed, 68 insertions(+), 27 deletions(-) (limited to 'layout') diff --git a/layout/box.go b/layout/box.go index 01a1143..13c7b2b 100644 --- a/layout/box.go +++ b/layout/box.go @@ -19,7 +19,7 @@ type box struct { // NewBox creates a familiar flexbox-like list layout. // It can be horizontal or vertical. -func NewBox(env gui.Env, contents []*gui.Env, options ...func(*box)) gui.Env { +func NewBox(contents []*gui.Env, options ...func(*box)) Layout { ret := &box{ Background: image.Black, Contents: contents, @@ -28,12 +28,7 @@ func NewBox(env gui.Env, contents []*gui.Env, options ...func(*box)) gui.Env { for _, f := range options { f(ret) } - - mux, env := NewMux(env, ret) - for _, item := range contents { - *item = mux.MakeEnv() - } - return env + return ret } // BoxVertical changes the otherwise horizontal Box to be vertical. @@ -67,6 +62,10 @@ func (g *box) Redraw(drw draw.Image, bounds image.Rectangle) { draw.Draw(drw, bounds, image.NewUniform(g.Background), image.ZP, draw.Src) } +func (g *box) Items() []*gui.Env { + return g.Contents +} + func (g *box) Lay(bounds image.Rectangle) []image.Rectangle { items := len(g.Contents) ret := make([]image.Rectangle, 0, items) diff --git a/layout/grid.go b/layout/grid.go index 9c79343..c90821a 100644 --- a/layout/grid.go +++ b/layout/grid.go @@ -18,7 +18,7 @@ type grid struct { // NewGrid creates a familiar flexbox-like grid layout. // Each row can be a different length. -func NewGrid(env gui.Env, contents [][]*gui.Env, options ...func(*grid)) gui.Env { +func NewGrid(contents [][]*gui.Env, options ...func(*grid)) Layout { ret := &grid{ Background: image.Black, Gap: 0, @@ -29,15 +29,7 @@ func NewGrid(env gui.Env, contents [][]*gui.Env, options ...func(*grid)) gui.Env for _, f := range options { f(ret) } - - mux, env := NewMux(env, ret) - for _, row := range contents { - for _, item := range row { - *item = mux.MakeEnv() - } - } - - return env + return ret } // GridBackground changes the background of the grid to a uniform color. @@ -73,6 +65,15 @@ func (g *grid) Redraw(drw draw.Image, bounds image.Rectangle) { draw.Draw(drw, bounds, image.NewUniform(g.Background), image.ZP, draw.Src) } +func (g *grid) Items() []*gui.Env { + // 32 should be more than enough for most grids + ret := make([]*gui.Env, 0, 32) + for _, row := range g.Contents { + ret = append(ret, row...) + } + return ret +} + func (g *grid) Lay(bounds image.Rectangle) []image.Rectangle { gap := g.Gap ret := make([]image.Rectangle, 0) diff --git a/layout/layout.go b/layout/layout.go index 987ee24..dfacca1 100644 --- a/layout/layout.go +++ b/layout/layout.go @@ -3,17 +3,22 @@ package layout import ( "image" "image/draw" + + "github.com/faiface/gui" ) // Layout represents any graphical layout // -// A Layout needs to be able to redraw itself with the Redraw method. -// Redraw() only draws the background or frame of the Layout, not the childs. +// Items returns the Layout's childs in whatever order. // // Lay represents the way to divide space among your childs. // It takes a parameter of how much space is available, // and returns where exactly to put its childs. +// The order must be the same as Items. +// +// Redraw only draws the background or frame of the Layout, not the childs. type Layout interface { + Items() []*gui.Env Lay(image.Rectangle) []image.Rectangle Redraw(draw.Image, image.Rectangle) } diff --git a/layout/mux.go b/layout/mux.go index 208664c..6c7ddaa 100644 --- a/layout/mux.go +++ b/layout/mux.go @@ -1,6 +1,7 @@ package layout import ( + "fmt" "image" "image/draw" "sync" @@ -24,9 +25,15 @@ type Mux struct { layout Layout } +// Layout returns the underlying Layout of the Mux. +func (mux *Mux) Layout() Layout { + return mux.layout +} + // NewMux should only be used internally by Layouts. -// It has mostly the same behaviour as gui.Mux, except for its use of and underlying Layout -// for modifying the gui.Resize events. to the childs. +// It has mostly the same behaviour as gui.Mux, except for its use of an underlying Layout +// for modifying the gui.Resize events sent to the childs. +// Also, you cannot make Envs manually, you must use FillLayout. func NewMux(env gui.Env, l Layout) (mux *Mux, master gui.Env) { drawChan := make(chan func(draw.Image) image.Rectangle) mux = &Mux{ @@ -34,7 +41,7 @@ func NewMux(env gui.Env, l Layout) (mux *Mux, master gui.Env) { draw: drawChan, } master, masterIn := mux.makeEnv(true) - events := make(chan gui.Event, 0) + events := make(chan gui.Event) mux.evIn = events go func() { for d := range drawChan { @@ -85,12 +92,36 @@ func NewMux(env gui.Env, l Layout) (mux *Mux, master gui.Env) { } mux.mu.Unlock() }() + + err := mux.FillLayout() + if err != nil { + panic(err) + } return } -func (mux *Mux) MakeEnv() gui.Env { - env, _ := mux.makeEnv(false) - return env +// FillLayout uses the mux to fill the Layout's Envs with suitable Envs. +// It's called automatically on NewMux, but you can call it again to fill +// the Envs that became nil, to change a child in the Layout. +func (mux *Mux) FillLayout() error { + nilptrs := 0 + filleditems := 0 + // err + for _, en := range mux.layout.Items() { + if en == nil { + nilptrs += 1 + continue + } + if *en != nil { + filleditems += 1 + continue + } + *en, _ = mux.makeEnv(false) + } + if nilptrs > 0 { + return fmt.Errorf("Mux: %d already filled and %d nil pointers", filleditems, nilptrs) + } + return nil } type muxEnv struct { diff --git a/layout/split.go b/layout/split.go index 4bf00a7..fb6d36e 100644 --- a/layout/split.go +++ b/layout/split.go @@ -1,13 +1,18 @@ package layout +import "fmt" + // SplitFunc represents a way to split a space among a number of elements. -// The space of the returned slice must be equal to the number of elements. +// The length of the returned slice must be equal to the number of elements. // The sum of all elements of the returned slice must be eqal to the space. type SplitFunc func(elements int, space int) []int -// EvenSplit implements SplitFunc to split a space (almost) evenly among the elements. +// EvenSplit is a SplitFunc used to split a space (almost) evenly among the elements. // It is almost evenly because width may not be divisible by elements. func EvenSplit(elements int, width int) []int { + if elements <= 0 { + panic(fmt.Errorf("EvenSplit: elements must be greater than 0")) + } ret := make([]int, 0, elements) for elements > 0 { v := width / elements -- cgit v1.2.3 From 736e35bd9b0c8f4f9649557e4b7a3085b4bdbe63 Mon Sep 17 00:00:00 2001 From: Clement Benard Date: Mon, 15 Jul 2019 15:44:34 +0200 Subject: Easier and more idiomatic Layout initializing --- layout/box.go | 93 ++++++++++++++++++----------------------------------- layout/doc.go | 10 ++++++ layout/grid.go | 98 ++++++++++++++++---------------------------------------- layout/layout.go | 5 --- layout/mux.go | 41 ++++++------------------ 5 files changed, 79 insertions(+), 168 deletions(-) create mode 100644 layout/doc.go (limited to 'layout') diff --git a/layout/box.go b/layout/box.go index 13c7b2b..24f0c0d 100644 --- a/layout/box.go +++ b/layout/box.go @@ -4,84 +4,53 @@ import ( "image" "image/color" "image/draw" - - "github.com/faiface/gui" ) -type box struct { - Contents []*gui.Env +type Box struct { + // Number of child elements + Length int + // Background changes the background of the Box to a uniform color. Background color.Color - Split SplitFunc - Gap int - - vertical bool -} - -// NewBox creates a familiar flexbox-like list layout. -// It can be horizontal or vertical. -func NewBox(contents []*gui.Env, options ...func(*box)) Layout { - ret := &box{ - Background: image.Black, - Contents: contents, - Split: EvenSplit, - } - for _, f := range options { - f(ret) - } - return ret -} + // Split changes the way the space is divided among the elements. + Split SplitFunc + // Gap changes the Box gap. + // The gap is identical everywhere (top, left, bottom, right). + Gap int -// BoxVertical changes the otherwise horizontal Box to be vertical. -func BoxVertical(b *box) { - b.vertical = true + // Vertical changes the otherwise horizontal Box to be vertical. + Vertical bool } -// BoxBackground changes the background of the box to a uniform color. -func BoxBackground(c color.Color) func(*box) { - return func(grid *box) { - grid.Background = c +func (b Box) Redraw(drw draw.Image, bounds image.Rectangle) { + col := b.Background + if col == nil { + col = image.Black } -} -// BoxSplit changes the way the space is divided among the elements. -func BoxSplit(split SplitFunc) func(*box) { - return func(grid *box) { - grid.Split = split - } + draw.Draw(drw, bounds, image.NewUniform(col), image.ZP, draw.Src) } -// BoxGap changes the box gap. -// The gap is identical everywhere (top, left, bottom, right). -func BoxGap(gap int) func(*box) { - return func(grid *box) { - grid.Gap = gap +func (b Box) Lay(bounds image.Rectangle) []image.Rectangle { + items := b.Length + gap := b.Gap + split := b.Split + if split == nil { + split = EvenSplit } -} - -func (g *box) Redraw(drw draw.Image, bounds image.Rectangle) { - draw.Draw(drw, bounds, image.NewUniform(g.Background), image.ZP, draw.Src) -} - -func (g *box) Items() []*gui.Env { - return g.Contents -} - -func (g *box) Lay(bounds image.Rectangle) []image.Rectangle { - items := len(g.Contents) ret := make([]image.Rectangle, 0, items) - if g.vertical { - spl := g.Split(items, bounds.Dy()-(g.Gap*(items+1))) - Y := bounds.Min.Y + g.Gap + if b.Vertical { + spl := split(items, bounds.Dy()-(gap*(items+1))) + Y := bounds.Min.Y + gap for _, item := range spl { - ret = append(ret, image.Rect(bounds.Min.X+g.Gap, Y, bounds.Max.X-g.Gap, Y+item)) - Y += item + g.Gap + ret = append(ret, image.Rect(bounds.Min.X+gap, Y, bounds.Max.X-gap, Y+item)) + Y += item + gap } } else { - spl := g.Split(items, bounds.Dx()-(g.Gap*(items+1))) - X := bounds.Min.X + g.Gap + spl := split(items, bounds.Dx()-(gap*(items+1))) + X := bounds.Min.X + gap for _, item := range spl { - ret = append(ret, image.Rect(X, bounds.Min.Y+g.Gap, X+item, bounds.Max.Y-g.Gap)) - X += item + g.Gap + ret = append(ret, image.Rect(X, bounds.Min.Y+gap, X+item, bounds.Max.Y-gap)) + X += item + gap } } return ret diff --git a/layout/doc.go b/layout/doc.go new file mode 100644 index 0000000..8305d43 --- /dev/null +++ b/layout/doc.go @@ -0,0 +1,10 @@ +/* +Package layout provides a Layout system for faiface/gui. + +The core of the package is the Layout interface, everything else is just +implementation. + +The Layouts represent a Layout, and the Mux makes them usable. +The Mux basically acts as a sort of driver. +*/ +package layout diff --git a/layout/grid.go b/layout/grid.go index c90821a..f3cced6 100644 --- a/layout/grid.go +++ b/layout/grid.go @@ -4,92 +4,50 @@ import ( "image" "image/color" "image/draw" - - "github.com/faiface/gui" ) -type grid struct { - Contents [][]*gui.Env +type Grid struct { + // Rows represents the number of childs of each row. + Rows []int + // Background represents the background of the grid as a uniform color. Background color.Color - Gap int - SplitX SplitFunc - SplitY SplitFunc -} - -// NewGrid creates a familiar flexbox-like grid layout. -// Each row can be a different length. -func NewGrid(contents [][]*gui.Env, options ...func(*grid)) Layout { - ret := &grid{ - Background: image.Black, - Gap: 0, - Contents: contents, - SplitX: EvenSplit, - SplitY: EvenSplit, - } - for _, f := range options { - f(ret) - } - return ret -} - -// GridBackground changes the background of the grid to a uniform color. -func GridBackground(c color.Color) func(*grid) { - return func(grid *grid) { - grid.Background = c - } -} - -// GridGap changes the grid gap. -// The gap is identical everywhere (top, left, bottom, right). -func GridGap(g int) func(*grid) { - return func(grid *grid) { - grid.Gap = g - } + // Gap represents the grid gap, equal on all sides. + Gap int + // SplitX represents the way the space is divided among the columns in each row. + SplitX SplitFunc + // SplitY represents the way the space is divided among the rows. + SplitY SplitFunc } -// GridSplitX changes the way the space is divided among the columns in each row. -func GridSplitX(split SplitFunc) func(*grid) { - return func(grid *grid) { - grid.SplitX = split +func (g Grid) Redraw(drw draw.Image, bounds image.Rectangle) { + col := g.Background + if col == nil { + col = image.Black } + draw.Draw(drw, bounds, image.NewUniform(col), image.ZP, draw.Src) } -// GridSplitY changes the way the space is divided among the rows. -func GridSplitY(split SplitFunc) func(*grid) { - return func(grid *grid) { - grid.SplitY = split +func (g Grid) Lay(bounds image.Rectangle) []image.Rectangle { + gap := g.Gap + rows := g.Rows + splitX := g.SplitX + if splitX == nil { + splitX = EvenSplit } -} - -func (g *grid) Redraw(drw draw.Image, bounds image.Rectangle) { - draw.Draw(drw, bounds, image.NewUniform(g.Background), image.ZP, draw.Src) -} - -func (g *grid) Items() []*gui.Env { - // 32 should be more than enough for most grids - ret := make([]*gui.Env, 0, 32) - for _, row := range g.Contents { - ret = append(ret, row...) + splitY := g.SplitY + if splitY == nil { + splitY = EvenSplit } - return ret -} -func (g *grid) Lay(bounds image.Rectangle) []image.Rectangle { - gap := g.Gap ret := make([]image.Rectangle, 0) - rows := len(g.Contents) - - rowsH := g.SplitY(rows, bounds.Dy()-(g.Gap*(rows+1))) - + rowsH := splitY(len(rows), bounds.Dy()-(gap*(len(rows)+1))) X := gap + bounds.Min.X Y := gap + bounds.Min.Y - for y, row := range g.Contents { - cols := len(row) + for y, cols := range rows { h := rowsH[y] - colsW := g.SplitX(cols, bounds.Dx()-(g.Gap*(cols+1))) + colsW := splitX(cols, bounds.Dx()-(gap*(cols+1))) X = gap + bounds.Min.X - for x := range row { - w := colsW[x] + for _, w := range colsW { ret = append(ret, image.Rect(X, Y, X+w, Y+h)) X += gap + w } diff --git a/layout/layout.go b/layout/layout.go index dfacca1..8b8e039 100644 --- a/layout/layout.go +++ b/layout/layout.go @@ -3,14 +3,10 @@ package layout import ( "image" "image/draw" - - "github.com/faiface/gui" ) // Layout represents any graphical layout // -// Items returns the Layout's childs in whatever order. -// // Lay represents the way to divide space among your childs. // It takes a parameter of how much space is available, // and returns where exactly to put its childs. @@ -18,7 +14,6 @@ import ( // // Redraw only draws the background or frame of the Layout, not the childs. type Layout interface { - Items() []*gui.Env Lay(image.Rectangle) []image.Rectangle Redraw(draw.Image, image.Rectangle) } diff --git a/layout/mux.go b/layout/mux.go index 6c7ddaa..2789878 100644 --- a/layout/mux.go +++ b/layout/mux.go @@ -1,9 +1,9 @@ package layout import ( - "fmt" "image" "image/draw" + "log" "sync" "github.com/faiface/gui" @@ -33,8 +33,7 @@ func (mux *Mux) Layout() Layout { // NewMux should only be used internally by Layouts. // It has mostly the same behaviour as gui.Mux, except for its use of an underlying Layout // for modifying the gui.Resize events sent to the childs. -// Also, you cannot make Envs manually, you must use FillLayout. -func NewMux(env gui.Env, l Layout) (mux *Mux, master gui.Env) { +func NewMux(env gui.Env, envs []*gui.Env, l Layout) (mux *Mux, master gui.Env) { drawChan := make(chan func(draw.Image) image.Rectangle) mux = &Mux{ layout: l, @@ -63,6 +62,12 @@ func NewMux(env gui.Env, l Layout) (mux *Mux, master gui.Env) { mux.mu.Lock() if resize, ok := e.(gui.Resize); ok { mux.lastResize = resize + lay := mux.layout.Lay(rect) + if len(lay) != len(mux.eventsIns) { + log.Printf("Lay of %T has %d elements while mux has %d, skipping\n", l, len(lay), len(envs)) + mux.mu.Unlock() + continue + } rect := resize.Rectangle @@ -73,7 +78,6 @@ func NewMux(env gui.Env, l Layout) (mux *Mux, master gui.Env) { } // Send appropriate resize Events to childs - lay := mux.layout.Lay(rect) for i, eventsIn := range mux.eventsIns { resize.Rectangle = lay[i] eventsIn <- resize @@ -93,35 +97,10 @@ func NewMux(env gui.Env, l Layout) (mux *Mux, master gui.Env) { mux.mu.Unlock() }() - err := mux.FillLayout() - if err != nil { - panic(err) - } - return -} - -// FillLayout uses the mux to fill the Layout's Envs with suitable Envs. -// It's called automatically on NewMux, but you can call it again to fill -// the Envs that became nil, to change a child in the Layout. -func (mux *Mux) FillLayout() error { - nilptrs := 0 - filleditems := 0 - // err - for _, en := range mux.layout.Items() { - if en == nil { - nilptrs += 1 - continue - } - if *en != nil { - filleditems += 1 - continue - } + for _, en := range envs { *en, _ = mux.makeEnv(false) } - if nilptrs > 0 { - return fmt.Errorf("Mux: %d already filled and %d nil pointers", filleditems, nilptrs) - } - return nil + return } type muxEnv struct { -- cgit v1.2.3 From 8b70878ccc7fe324f3647e56503a37f3780f9d41 Mon Sep 17 00:00:00 2001 From: Clement Benard Date: Mon, 15 Jul 2019 16:19:19 +0200 Subject: now working --- layout/mux.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'layout') diff --git a/layout/mux.go b/layout/mux.go index 2789878..d56d266 100644 --- a/layout/mux.go +++ b/layout/mux.go @@ -62,6 +62,7 @@ func NewMux(env gui.Env, envs []*gui.Env, l Layout) (mux *Mux, master gui.Env) { mux.mu.Lock() if resize, ok := e.(gui.Resize); ok { mux.lastResize = resize + rect := resize.Rectangle lay := mux.layout.Lay(rect) if len(lay) != len(mux.eventsIns) { log.Printf("Lay of %T has %d elements while mux has %d, skipping\n", l, len(lay), len(envs)) @@ -69,8 +70,6 @@ func NewMux(env gui.Env, envs []*gui.Env, l Layout) (mux *Mux, master gui.Env) { continue } - rect := resize.Rectangle - // Redraw self mux.draw <- func(drw draw.Image) image.Rectangle { mux.layout.Redraw(drw, rect) -- cgit v1.2.3 From 8d183ef96a57e3a2f42c0cb4ec0ab4c256e0d47e Mon Sep 17 00:00:00 2001 From: Clement Benard Date: Wed, 7 Aug 2019 16:02:33 +0200 Subject: Made the layout package actually usable --- layout/box.go | 57 ---------------------- layout/grid.go | 95 ++++++++++++++++++++++++++++-------- layout/intercepter.go | 43 ++++++++++++++++ layout/layout.go | 9 ++-- layout/mux.go | 16 ++---- layout/scroller.go | 133 ++++++++++++++++++++++++++++++++++++++++++++++++++ layout/split.go | 6 ++- 7 files changed, 265 insertions(+), 94 deletions(-) delete mode 100644 layout/box.go create mode 100644 layout/intercepter.go create mode 100644 layout/scroller.go (limited to 'layout') diff --git a/layout/box.go b/layout/box.go deleted file mode 100644 index 24f0c0d..0000000 --- a/layout/box.go +++ /dev/null @@ -1,57 +0,0 @@ -package layout - -import ( - "image" - "image/color" - "image/draw" -) - -type Box struct { - // Number of child elements - Length int - // Background changes the background of the Box to a uniform color. - Background color.Color - // Split changes the way the space is divided among the elements. - Split SplitFunc - // Gap changes the Box gap. - // The gap is identical everywhere (top, left, bottom, right). - Gap int - - // Vertical changes the otherwise horizontal Box to be vertical. - Vertical bool -} - -func (b Box) Redraw(drw draw.Image, bounds image.Rectangle) { - col := b.Background - if col == nil { - col = image.Black - } - - draw.Draw(drw, bounds, image.NewUniform(col), image.ZP, draw.Src) -} - -func (b Box) Lay(bounds image.Rectangle) []image.Rectangle { - items := b.Length - gap := b.Gap - split := b.Split - if split == nil { - split = EvenSplit - } - ret := make([]image.Rectangle, 0, items) - if b.Vertical { - spl := split(items, bounds.Dy()-(gap*(items+1))) - Y := bounds.Min.Y + gap - for _, item := range spl { - ret = append(ret, image.Rect(bounds.Min.X+gap, Y, bounds.Max.X-gap, Y+item)) - Y += item + gap - } - } else { - spl := split(items, bounds.Dx()-(gap*(items+1))) - X := bounds.Min.X + gap - for _, item := range spl { - ret = append(ret, image.Rect(X, bounds.Min.Y+gap, X+item, bounds.Max.Y-gap)) - X += item + gap - } - } - return ret -} diff --git a/layout/grid.go b/layout/grid.go index f3cced6..3ff8112 100644 --- a/layout/grid.go +++ b/layout/grid.go @@ -4,8 +4,15 @@ import ( "image" "image/color" "image/draw" + "log" + + "github.com/faiface/gui" ) +var _ Layout = Grid{} + +// Grid represents a grid with rows and columns in each row. +// Each row can be a different length. type Grid struct { // Rows represents the number of childs of each row. Rows []int @@ -13,42 +20,92 @@ type Grid struct { Background color.Color // Gap represents the grid gap, equal on all sides. Gap int - // SplitX represents the way the space is divided among the columns in each row. - SplitX SplitFunc - // SplitY represents the way the space is divided among the rows. - SplitY SplitFunc + // Split represents the way the space is divided among the columns in each row. + Split SplitFunc + // SplitRows represents the way the space is divided among the rows. + SplitRows SplitFunc + + Margin int + Border int + BorderColor color.Color + + // Flip represents the orientation of the grid. + // When false, rows are spread in the Y axis and columns in the X axis. + // When true, rows are spread in the X axis and columns in the Y axis. + Flip bool } -func (g Grid) Redraw(drw draw.Image, bounds image.Rectangle) { +func (g Grid) redraw(drw draw.Image, bounds image.Rectangle) { col := g.Background if col == nil { - col = image.Black + col = color.Black + } + if g.Border > 0 { + bcol := g.BorderColor + if bcol == nil { + bcol = color.Black + } + draw.Draw(drw, bounds, image.NewUniform(bcol), image.ZP, draw.Src) } - draw.Draw(drw, bounds, image.NewUniform(col), image.ZP, draw.Src) + draw.Draw(drw, bounds.Inset(g.Border), image.NewUniform(col), image.ZP, draw.Src) +} + +func (g Grid) Intercept(env gui.Env) gui.Env { + return RedrawIntercepter{g.redraw}.Intercept(env) } func (g Grid) Lay(bounds image.Rectangle) []image.Rectangle { gap := g.Gap rows := g.Rows - splitX := g.SplitX - if splitX == nil { - splitX = EvenSplit + splitMain := g.Split + if splitMain == nil { + splitMain = EvenSplit + } + splitSec := g.SplitRows + if splitSec == nil { + splitSec = EvenSplit } - splitY := g.SplitY - if splitY == nil { - splitY = EvenSplit + margin := g.Margin + flip := g.Flip + if margin+gap < 0 { + log.Println("Grid goes out of bounds") + } + if margin+gap < g.Border { + log.Println("Grid border will not be shown properly") } ret := make([]image.Rectangle, 0) - rowsH := splitY(len(rows), bounds.Dy()-(gap*(len(rows)+1))) - X := gap + bounds.Min.X - Y := gap + bounds.Min.Y + + // Sorry it's not very understandable + var H, W int + var mX, mY int + if flip { + H = bounds.Dx() + W = bounds.Dy() + mX = bounds.Min.Y + mY = bounds.Min.X + } else { + H = bounds.Dy() + W = bounds.Dx() + mX = bounds.Min.X + mY = bounds.Min.Y + } + rowsH := splitSec(len(rows), H-(gap*(len(rows)+1))-margin*2) + var X int + var Y int + Y = gap + mY + margin for y, cols := range rows { h := rowsH[y] - colsW := splitX(cols, bounds.Dx()-(gap*(cols+1))) - X = gap + bounds.Min.X + colsW := splitMain(cols, W-(gap*(cols+1))-margin*2) + X = gap + mX + margin for _, w := range colsW { - ret = append(ret, image.Rect(X, Y, X+w, Y+h)) + var r image.Rectangle + if flip { + r = image.Rect(Y, X, Y+h, X+w) + } else { + r = image.Rect(X, Y, X+w, Y+h) + } + ret = append(ret, r) X += gap + w } Y += gap + h diff --git a/layout/intercepter.go b/layout/intercepter.go new file mode 100644 index 0000000..eee7aff --- /dev/null +++ b/layout/intercepter.go @@ -0,0 +1,43 @@ +package layout + +import ( + "image" + "image/draw" + + "github.com/faiface/gui" +) + +// Intercepter represents an element that can interact with Envs. +// An Intercepter can modify Events, stop them or emit arbitrary ones. +// It can also put itself in the draw pipeline, for throttling very +// expensive draw calls for example. +type Intercepter interface { + Intercept(gui.Env) gui.Env +} + +var _ Intercepter = RedrawIntercepter{} + +// RedrawIntercepter is a basic Intercepter, it is meant for use in simple Layouts +// that only need to redraw themselves. +type RedrawIntercepter struct { + Redraw func(draw.Image, image.Rectangle) +} + +// Intercept implements Intercepter +func (ri RedrawIntercepter) Intercept(env gui.Env) gui.Env { + out, in := gui.MakeEventsChan() + go func() { + for e := range env.Events() { + in <- e + if resize, ok := e.(gui.Resize); ok { + env.Draw() <- func(drw draw.Image) image.Rectangle { + bounds := resize.Rectangle + ri.Redraw(drw, bounds) + return bounds + } + } + } + }() + ret := &muxEnv{out, env.Draw()} + return ret +} diff --git a/layout/layout.go b/layout/layout.go index 8b8e039..4d8e616 100644 --- a/layout/layout.go +++ b/layout/layout.go @@ -2,7 +2,6 @@ package layout import ( "image" - "image/draw" ) // Layout represents any graphical layout @@ -10,10 +9,12 @@ import ( // Lay represents the way to divide space among your childs. // It takes a parameter of how much space is available, // and returns where exactly to put its childs. -// The order must be the same as Items. // -// Redraw only draws the background or frame of the Layout, not the childs. +// Intercept transforms an Env channel to another. +// This way the Layout can emit its own Events, re-emit previous ones, +// or even stop an event from propagating, think win.MoScroll. +// It can be a no-op. type Layout interface { Lay(image.Rectangle) []image.Rectangle - Redraw(draw.Image, image.Rectangle) + Intercepter } diff --git a/layout/mux.go b/layout/mux.go index d56d266..8eb19d6 100644 --- a/layout/mux.go +++ b/layout/mux.go @@ -21,7 +21,6 @@ type Mux struct { eventsIns []chan<- gui.Event draw chan<- func(draw.Image) image.Rectangle - evIn chan<- gui.Event layout Layout } @@ -33,7 +32,8 @@ func (mux *Mux) Layout() Layout { // NewMux should only be used internally by Layouts. // It has mostly the same behaviour as gui.Mux, except for its use of an underlying Layout // for modifying the gui.Resize events sent to the childs. -func NewMux(env gui.Env, envs []*gui.Env, l Layout) (mux *Mux, master gui.Env) { +func NewMux(ev gui.Env, envs []*gui.Env, l Layout) (mux *Mux, master gui.Env) { + env := l.Intercept(ev) drawChan := make(chan func(draw.Image) image.Rectangle) mux = &Mux{ layout: l, @@ -41,7 +41,6 @@ func NewMux(env gui.Env, envs []*gui.Env, l Layout) (mux *Mux, master gui.Env) { } master, masterIn := mux.makeEnv(true) events := make(chan gui.Event) - mux.evIn = events go func() { for d := range drawChan { env.Draw() <- d @@ -64,18 +63,12 @@ func NewMux(env gui.Env, envs []*gui.Env, l Layout) (mux *Mux, master gui.Env) { mux.lastResize = resize rect := resize.Rectangle lay := mux.layout.Lay(rect) - if len(lay) != len(mux.eventsIns) { - log.Printf("Lay of %T has %d elements while mux has %d, skipping\n", l, len(lay), len(envs)) + if len(lay) < len(envs) { + log.Printf("Lay of %T is not large enough (%d) for %d childs, skipping\n", l, len(lay), len(envs)) mux.mu.Unlock() continue } - // Redraw self - mux.draw <- func(drw draw.Image) image.Rectangle { - mux.layout.Redraw(drw, rect) - return rect - } - // Send appropriate resize Events to childs for i, eventsIn := range mux.eventsIns { resize.Rectangle = lay[i] @@ -172,7 +165,6 @@ func (mux *Mux) makeEnv(master bool) (env gui.Env, eventsIn chan<- gui.Event) { mux.eventsIns = append(mux.eventsIns[:i], mux.eventsIns[i+1:]...) } mux.mu.Unlock() - mux.evIn <- mux.lastResize } }() diff --git a/layout/scroller.go b/layout/scroller.go new file mode 100644 index 0000000..151a4f2 --- /dev/null +++ b/layout/scroller.go @@ -0,0 +1,133 @@ +package layout + +import ( + "image" + "image/color" + "image/draw" + // "log" + "sync" + + "github.com/faiface/gui" + "github.com/faiface/gui/win" +) + +var _ Layout = &Scroller{} + +type Scroller struct { + Background color.Color + Length int + ChildHeight int + Offset int + Gap int + Vertical bool +} + +func (s Scroller) redraw(drw draw.Image, bounds image.Rectangle) { + col := s.Background + if col == nil { + col = image.Black + } + draw.Draw(drw, bounds, image.NewUniform(col), image.ZP, draw.Src) +} + +func clamp(val, a, b int) int { + if a > b { + if val < b { + return b + } + if val > a { + return a + } + } else { + if val > b { + return b + } + if val < a { + return a + } + } + return val +} + +func (s *Scroller) Intercept(env gui.Env) gui.Env { + evs := env.Events() + out, in := gui.MakeEventsChan() + drawChan := make(chan func(draw.Image) image.Rectangle) + ret := &muxEnv{out, drawChan} + var lastResize gui.Resize + var img draw.Image + img = image.NewRGBA(image.ZR) + var mu sync.Mutex + var over bool + + go func() { + for dc := range drawChan { + mu.Lock() + // draw.Draw will not draw out of bounds, call should be inexpensive if element not visible + res := dc(img) + // Only send a draw call up if visibly changed + if res.Intersect(img.Bounds()) != image.ZR { + env.Draw() <- func(drw draw.Image) image.Rectangle { + draw.Draw(drw, lastResize.Rectangle, img, lastResize.Rectangle.Min, draw.Over) + return img.Bounds() + } + } + mu.Unlock() + } + }() + + go func() { + for ev := range evs { + switch ev := ev.(type) { + case win.MoMove: + mu.Lock() + over = ev.Point.In(lastResize.Rectangle) + mu.Unlock() + case win.MoScroll: + if !over { + continue + } + mu.Lock() + oldoff := s.Offset + v := s.Length*s.ChildHeight + ((s.Length + 1) * s.Gap) + if s.Vertical { + h := lastResize.Dx() + s.Offset = clamp(s.Offset+ev.Point.X*16, h-v, 0) + } else { + h := lastResize.Dy() + s.Offset = clamp(s.Offset+ev.Point.Y*16, h-v, 0) + } + if oldoff != s.Offset { + s.redraw(img, img.Bounds()) + in <- lastResize + } + mu.Unlock() + case gui.Resize: + mu.Lock() + lastResize = ev + img = image.NewRGBA(lastResize.Rectangle) + s.redraw(img, img.Bounds()) + mu.Unlock() + in <- ev + default: + in <- ev + } + } + }() + return ret +} + +func (s Scroller) Lay(bounds image.Rectangle) []image.Rectangle { + items := s.Length + ch := s.ChildHeight + gap := s.Gap + + ret := make([]image.Rectangle, items) + Y := bounds.Min.Y + s.Offset + gap + for i := 0; i < items; i++ { + r := image.Rect(bounds.Min.X+gap, Y, bounds.Max.X-gap, Y+ch) + ret[i] = r + Y += ch + gap + } + return ret +} diff --git a/layout/split.go b/layout/split.go index fb6d36e..db04225 100644 --- a/layout/split.go +++ b/layout/split.go @@ -7,18 +7,20 @@ import "fmt" // The sum of all elements of the returned slice must be eqal to the space. type SplitFunc func(elements int, space int) []int +var _ SplitFunc = EvenSplit + // EvenSplit is a SplitFunc used to split a space (almost) evenly among the elements. // It is almost evenly because width may not be divisible by elements. func EvenSplit(elements int, width int) []int { if elements <= 0 { panic(fmt.Errorf("EvenSplit: elements must be greater than 0")) } - ret := make([]int, 0, elements) + ret := make([]int, elements) for elements > 0 { v := width / elements width -= v elements -= 1 - ret = append(ret, v) + ret[elements] = v } return ret } -- cgit v1.2.3