aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorClement Benard <contact@clementbenard.com>2019-07-09 10:59:38 +0200
committerClement Benard <contact@clementbenard.com>2019-07-09 10:59:38 +0200
commit1223e277009005337243ca991cb54dd75bf723a7 (patch)
treeaa4863dc2a08649c825fd28843940ef5d60beba1
parent3a216b96b6a7c80275a2516e7de82d9b2ffc96df (diff)
downloadgui-1223e277009005337243ca991cb54dd75bf723a7.zip
Layout system remaking
-rwxr-xr-xexamples/layout/layoutbin0 -> 5570192 bytes
-rw-r--r--examples/layout/main.go122
-rw-r--r--layout/box.go99
-rw-r--r--layout/fixedgrid/fixedgrid.go88
-rw-r--r--layout/grid.go92
-rw-r--r--layout/layout.go168
-rw-r--r--layout/mux.go150
7 files changed, 446 insertions, 273 deletions
diff --git a/examples/layout/layout b/examples/layout/layout
new file mode 100755
index 0000000..050fbdf
--- /dev/null
+++ b/examples/layout/layout
Binary files differ
diff --git a/examples/layout/main.go b/examples/layout/main.go
index 47364a9..20d82c2 100644
--- a/examples/layout/main.go
+++ b/examples/layout/main.go
@@ -7,7 +7,7 @@ import (
"time"
"github.com/faiface/gui"
- "github.com/faiface/gui/fixedgrid"
+ "github.com/faiface/gui/layout"
"github.com/faiface/gui/win"
"github.com/faiface/mainthread"
"golang.org/x/image/colornames"
@@ -81,39 +81,105 @@ func run() {
ButtonUp: colornames.Lightgrey,
ButtonDown: colornames.Grey,
}
- w, err := win.New(win.Title("gui test"),
- win.Resizable(),
- )
+ w, err := win.New(win.Title("gui test")) // win.Resizable(),
+
if err != nil {
panic(err)
}
mux, env := gui.NewMux(w)
- gr := fixedgrid.New(mux.MakeEnv(),
- fixedgrid.Rows(5),
- fixedgrid.Columns(2),
- fixedgrid.Gap(10),
+ var (
+ top gui.Env
+ left, right gui.Env
+ bottomLeft, bottom, bottomRight gui.Env
+ )
+ layout.NewGrid(
+ mux.MakeEnv(),
+ [][]*gui.Env{
+ {&top},
+ {&left, &right},
+ {&bottomLeft, &bottom, &bottomRight},
+ },
+ layout.GridGap(10),
+ layout.GridBackground(colornames.Sandybrown),
+ layout.GridSplitY(func(els int, width int) []int {
+ ret := make([]int, els)
+ total := 0
+ for i := 0; i < els; i++ {
+ if i == els-1 {
+ ret[i] = width - total
+ } else {
+ v := (width - total) / 2
+ ret[i] = v
+ total += v
+ }
+ }
+ return ret
+ }),
+ )
+ go Blinker(right, false)
+ go Blinker(left, false)
+ go Blinker(bottomRight, false)
+
+ var (
+ b1, b2, b3, b4, b5, b6 gui.Env
)
- log.Print(gr)
- go Blinker(gr.GetEnv("0;0"), false)
- go Blinker(gr.GetEnv("0;1"), true)
- go Blinker(gr.GetEnv("1;1"), false)
- go Blinker(gr.GetEnv("0;2"), false)
- go Blinker(gr.GetEnv("0;3"), false)
- go Blinker(gr.GetEnv("0;4"), false)
- sgr := fixedgrid.New(gr.GetEnv("1;0"),
- fixedgrid.Columns(3),
- fixedgrid.Gap(4),
- fixedgrid.Background(colornames.Darkgrey),
+ layout.NewBox(
+ top,
+ []*gui.Env{
+ &b1, &b2, &b3,
+ },
+ layout.BoxGap(10),
+ layout.BoxBackground(colornames.Lightblue),
)
- go Button(sgr.GetEnv("0;0"), theme, "Hey", func() {
- log.Print("hey")
- })
- go Button(sgr.GetEnv("1;0"), theme, "Ho", func() {
- log.Print("ho")
- })
- go Button(sgr.GetEnv("2;0"), theme, "Hu", func() {
- log.Print("hu")
- })
+ go Blinker(b1, false)
+ go Blinker(b2, false)
+ layout.NewBox(
+ b3,
+ []*gui.Env{
+ &b4, &b5, &b6,
+ },
+ layout.BoxVertical,
+ layout.BoxBackground(colornames.Pink),
+ layout.BoxGap(4),
+ layout.BoxSplit(func(els int, width int) []int {
+ ret := make([]int, els)
+ total := 0
+ for i := 0; i < els; i++ {
+ if i == els-1 {
+ ret[i] = width - total
+ } else {
+ v := (width - total) / 2
+ ret[i] = v
+ total += v
+ }
+ }
+ return ret
+ }),
+ )
+ go Blinker(b4, false)
+ go Blinker(b5, false)
+ go Blinker(b6, false)
+
+ var (
+ btn1, btn2, btn3 gui.Env
+ )
+ layout.NewGrid(
+ bottom,
+ [][]*gui.Env{
+ {&btn1, &btn2, &btn3},
+ },
+ layout.GridGap(4),
+ layout.GridBackground(colornames.Darkgrey),
+ )
+ btn := func(env gui.Env, name string) {
+ Button(env, theme, name, func() {
+ log.Print(name)
+ })
+ }
+ go btn(btn1, "Hey")
+ go btn(btn2, "Ho")
+ go btn(btn3, "Hu")
+
// we use the master env now, w is used by the mux
for event := range env.Events() {
switch event.(type) {
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
+}