diff options
| author | Clement Benard <contact@clementbenard.com> | 2019-08-07 16:02:33 +0200 |
|---|---|---|
| committer | Clement Benard <contact@clementbenard.com> | 2019-08-07 16:02:33 +0200 |
| commit | 8d183ef96a57e3a2f42c0cb4ec0ab4c256e0d47e (patch) | |
| tree | 4fa644f93ceeb5c102c5c1dcf85105890c0cf25b | |
| parent | 8b70878ccc7fe324f3647e56503a37f3780f9d41 (diff) | |
| download | gui-8d183ef96a57e3a2f42c0cb4ec0ab4c256e0d47e.zip | |
Made the layout package actually usable
| -rw-r--r-- | examples/layout/blinker.go | 37 | ||||
| -rw-r--r-- | examples/layout/button.go | 7 | ||||
| -rw-r--r-- | examples/layout/card.go | 24 | ||||
| -rw-r--r-- | examples/layout/label.go | 35 | ||||
| -rwxr-xr-x | examples/layout/layout | bin | 5564792 -> 5614432 bytes | |||
| -rw-r--r-- | examples/layout/main.go | 103 | ||||
| -rw-r--r-- | layout/box.go | 57 | ||||
| -rw-r--r-- | layout/grid.go | 95 | ||||
| -rw-r--r-- | layout/intercepter.go | 43 | ||||
| -rw-r--r-- | layout/layout.go | 9 | ||||
| -rw-r--r-- | layout/mux.go | 16 | ||||
| -rw-r--r-- | layout/scroller.go | 133 | ||||
| -rw-r--r-- | layout/split.go | 6 |
13 files changed, 420 insertions, 145 deletions
diff --git a/examples/layout/blinker.go b/examples/layout/blinker.go index 8c380c3..65dce43 100644 --- a/examples/layout/blinker.go +++ b/examples/layout/blinker.go @@ -2,13 +2,15 @@ package main import ( "image" + "image/color" "image/draw" "log" + "math/rand" + "sync" "time" "github.com/faiface/gui" "github.com/faiface/gui/win" - "golang.org/x/image/colornames" ) func Blinker(env gui.Env) { @@ -17,23 +19,33 @@ func Blinker(env gui.Env) { log.Print("recovered blinker") } }() - - var r image.Rectangle - var visible bool = true - - redraw := func() func(draw.Image) image.Rectangle { + buf := make([]byte, 3) + rand.Read(buf) + defaultColor := image.NewUniform(color.RGBA{buf[0], buf[1], buf[2], 255}) + rand.Read(buf) + blinkColor := image.NewUniform(color.RGBA{buf[0], buf[1], buf[2], 255}) + redraw := func(r image.Rectangle, visible bool) func(draw.Image) image.Rectangle { return func(drw draw.Image) image.Rectangle { + if r == image.ZR { + return r + } if visible { - draw.Draw(drw, r, image.White, image.ZP, draw.Src) + draw.Draw(drw, r, defaultColor, image.ZP, draw.Src) } else { - draw.Draw(drw, r, &image.Uniform{colornames.Firebrick}, image.ZP, draw.Src) + draw.Draw(drw, r, blinkColor, image.ZP, draw.Src) } return r } } + var mu sync.Mutex + var ( + r image.Rectangle + visible bool = true + ) + // first we draw a white rectangle - env.Draw() <- redraw() + // env.Draw() <- redraw(b) func() { for event := range env.Events() { switch event := event.(type) { @@ -41,15 +53,18 @@ func Blinker(env gui.Env) { if event.Point.In(r) { go func() { for i := 0; i < 6; i++ { + mu.Lock() visible = !visible - env.Draw() <- redraw() + env.Draw() <- redraw(r, visible) + mu.Unlock() + time.Sleep(time.Second / 3) } }() } case gui.Resize: r = event.Rectangle - env.Draw() <- redraw() + env.Draw() <- redraw(r, visible) } } }() diff --git a/examples/layout/button.go b/examples/layout/button.go index 0693e06..cf13c3d 100644 --- a/examples/layout/button.go +++ b/examples/layout/button.go @@ -40,6 +40,13 @@ func Button(env gui.Env, theme *Theme, text string, action func()) { r = e.Rectangle env.Draw() <- redraw(r, over, pressed) + case win.MoMove: + nover := e.Point.In(r) + if nover != over { + over = nover + env.Draw() <- redraw(r, over, pressed) + } + case win.MoDown: newPressed := e.Point.In(r) if newPressed != pressed { diff --git a/examples/layout/card.go b/examples/layout/card.go new file mode 100644 index 0000000..501e4e3 --- /dev/null +++ b/examples/layout/card.go @@ -0,0 +1,24 @@ +package main + +import ( + "github.com/faiface/gui" + "github.com/faiface/gui/layout" + "golang.org/x/image/colornames" +) + +func Card(env gui.Env, theme *Theme, title, content string) { + box := layout.Grid{ + Rows: []int{1, 1}, + // Flip: true, + // Gap: 4, + Background: colornames.Pink, + } + fields := makeEnvPtr(2) + layout.NewMux(env, + fields, + box, + ) + go Label(*fields[0], theme, title, colornames.Lightgray) + go Label(*fields[1], theme, content, colornames.Slategray) + // go Blinker(*fields[1]) +} diff --git a/examples/layout/label.go b/examples/layout/label.go new file mode 100644 index 0000000..f46f25f --- /dev/null +++ b/examples/layout/label.go @@ -0,0 +1,35 @@ +package main + +import ( + "image" + "image/color" + "image/draw" + + "github.com/faiface/gui" +) + +func Label(env gui.Env, theme *Theme, text string, colr color.Color) { + textImg := MakeTextImage(text, theme.Face, theme.Text) + + redraw := func(r image.Rectangle) func(draw.Image) image.Rectangle { + return func(drw draw.Image) image.Rectangle { + draw.Draw(drw, r, &image.Uniform{colr}, image.ZP, draw.Src) + DrawLeftCentered(drw, r.Add(image.Pt(5, 0)), textImg, draw.Over) + return r + } + } + + var ( + r image.Rectangle + ) + + for e := range env.Events() { + switch e := e.(type) { + case gui.Resize: + r = e.Rectangle + env.Draw() <- redraw(r) + } + } + + close(env.Draw()) +} diff --git a/examples/layout/layout b/examples/layout/layout Binary files differindex 0d51021..e7e9c5b 100755 --- a/examples/layout/layout +++ b/examples/layout/layout diff --git a/examples/layout/main.go b/examples/layout/main.go index c672e63..83a170d 100644 --- a/examples/layout/main.go +++ b/examples/layout/main.go @@ -1,9 +1,11 @@ package main import ( + "fmt" "image" "image/draw" "log" + "os" "time" "github.com/faiface/gui" @@ -14,6 +16,14 @@ import ( "golang.org/x/image/font/gofont/goregular" ) +func makeEnvPtr(n int) []*gui.Env { + elsp := make([]*gui.Env, n) + for i := 0; i < len(elsp); i++ { + elsp[i] = new(gui.Env) + } + return elsp +} + func run() { face, err := TTFToFace(goregular.TTF, 18) if err != nil { @@ -26,7 +36,8 @@ func run() { Text: colornames.Black, Highlight: colornames.Blueviolet, ButtonUp: colornames.Lightgrey, - ButtonDown: colornames.Grey, + ButtonOver: colornames.Grey, + ButtonDown: colornames.Dimgrey, } w, err := win.New(win.Title("gui test")) // win.Resizable(), if err != nil { @@ -39,7 +50,7 @@ func run() { // Hack for non-reparenting window managers (I think) e := mux.MakeEnv() for { - time.Sleep(time.Second / 5) + time.Sleep(time.Second / 10) e.Draw() <- func(drw draw.Image) image.Rectangle { r := image.Rect(0, 0, 10, 10) draw.Draw(drw, r, image.Transparent, image.ZP, draw.Over) @@ -60,21 +71,21 @@ func run() { &left, &right, &bottomLeft, &bottom, &bottomRight}, layout.Grid{ - Rows: []int{1, 2, 3}, - Gap: 10, - Background: colornames.Sandybrown, - SplitY: func(els int, width int) []int { + Rows: []int{1, 2, 3}, + Gap: 10, + Margin: -6, + Border: 1, + // Flip: true, + BorderColor: image.White, + Background: colornames.Sandybrown, + SplitRows: 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 - } + for i := 0; i < els-1; i++ { + ret[i] = (width - total) / 2 + total += ret[i] } + ret[els-1] = width - total return ret }, }, @@ -83,22 +94,36 @@ func run() { go Blinker(left) go Blinker(bottomRight) - var ( - b1, b2, b3, b4, b5, b6 gui.Env - ) + subGrid := makeEnvPtr(3) layout.NewMux(top, - []*gui.Env{&b1, &b2, &b3}, - layout.Box{ - Length: 3, + subGrid, + layout.Grid{ + Rows: []int{len(subGrid)}, Gap: 10, Background: colornames.Lightblue, }, ) - go Blinker(b1) - go Blinker(b2) - box := layout.Box{ - Length: 3, - Vertical: true, + + elsp := makeEnvPtr(100) + scrl := &layout.Scroller{ + Background: colornames.Red, + Length: len(elsp), + Gap: 2, + ChildHeight: 80, + } + layout.NewMux(*subGrid[0], + elsp, + scrl, + ) + for i, el := range elsp { + // go Blinker(*el) + go Card(*el, theme, "hello", fmt.Sprintf("I'm card #%d", i)) + } + + go Blinker(*subGrid[1]) + box := layout.Grid{ + Rows: []int{3}, + Flip: true, Gap: 4, Background: colornames.Pink, Split: func(els int, width int) []int { @@ -113,28 +138,25 @@ func run() { return ret }, } - - layout.NewMux(b3, - []*gui.Env{ - &b4, &b5, &b6, - }, + blinkers := makeEnvPtr(3) + layout.NewMux(*subGrid[2], + blinkers, box, ) - go Blinker(b4) - go Blinker(b5) - go Blinker(b6) + go Blinker(*blinkers[0]) + go Blinker(*blinkers[1]) + go Blinker(*blinkers[2]) - var ( - btn1, btn2, btn3 gui.Env - ) + btns := makeEnvPtr(3) layout.NewMux( bottom, - []*gui.Env{&btn1, &btn2, &btn3}, + btns, layout.Grid{ Rows: []int{2, 1}, Background: colornames.Darkgrey, Gap: 4, + Flip: true, }, ) btn := func(env gui.Env, name string) { @@ -142,15 +164,16 @@ func run() { log.Print(name) }) } - go btn(btn1, "Hey") - go btn(btn2, "Ho") - go btn(btn3, "Hu") + go btn(*btns[0], "Hey") + go btn(*btns[1], "Ho") + go btn(*btns[2], "Hu") // we use the master env now, w is used by the mux for event := range env.Events() { switch event.(type) { case win.WiClose: close(env.Draw()) + os.Exit(0) } } } 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 } |