all: serialize ops

Pros:
- Much less per-frame garbage
- Allow future preprocessing of ops while building it
- Much fewer interface calls and pointer chasing
- Allow future serialization of ops for remote rendering

Cons:
- Slightly clumsier API

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2019-04-24 13:33:01 +02:00
parent 7b6e1ce35a
commit 252e058766
21 changed files with 809 additions and 385 deletions
+64 -67
View File
@@ -162,6 +162,7 @@ func init() {
func (a *App) run() error { func (a *App) run() error {
a.w.Profiling = *stats a.w.Profiling = *stats
ops := new(ui.Ops)
for a.w.IsAlive() { for a.w.IsAlive() {
select { select {
case users := <-a.updateUsers: case users := <-a.updateUsers:
@@ -194,23 +195,23 @@ func (a *App) run() error {
} }
} }
case app.Draw: case app.Draw:
ops.Reset()
a.cfg = e.Config a.cfg = e.Config
a.faces.Cfg = a.cfg a.faces.Cfg = a.cfg
cs := layout.ExactConstraints(a.w.Size()) cs := layout.ExactConstraints(a.w.Size())
root, _ := a.Layout(cs) a.Layout(ops, cs)
if a.w.Profiling { if a.w.Profiling {
op, _ := layout.Align( layout.Align(
layout.NE, layout.NE,
layout.Margin(a.cfg, layout.Margin(a.cfg,
layout.Margins{Top: ui.Dp(16)}, layout.Margins{Top: ui.Dp(16)},
text.Label{Src: textColor, Face: a.face(fonts.mono, 8), Text: a.w.Timings()}, text.Label{Src: textColor, Face: a.face(fonts.mono, 8), Text: a.w.Timings()},
), ),
).Layout(cs) ).Layout(ops, cs)
root = ui.Ops{root, op}
} }
a.w.Draw(root) a.w.Draw(ops)
a.w.SetTextInput(a.kqueue.Frame(root)) a.w.SetTextInput(a.kqueue.Frame(ops))
a.pqueue.Frame(root) a.pqueue.Frame(ops)
a.faces.Frame() a.faces.Frame()
} }
} }
@@ -361,12 +362,12 @@ func (a *App) face(f *sfnt.Font, size float32) text.Face {
return a.faces.For(f, ui.Sp(size)) return a.faces.For(f, ui.Sp(size))
} }
func (a *App) Layout(cs layout.Constraints) (ui.Op, layout.Dimens) { func (a *App) Layout(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
if a.selectedUser == nil { if a.selectedUser == nil {
return a.layoutUsers(cs) return a.layoutUsers(ops, cs)
} else { } else {
a.selectedUser.Update(a.cfg, a.pqueue) a.selectedUser.Update(a.cfg, a.pqueue)
return a.selectedUser.Layout(cs) return a.selectedUser.Layout(ops, cs)
} }
} }
@@ -387,22 +388,21 @@ func (up *userPage) Update(cfg *ui.Config, pqueue pointer.Events) {
up.commitsList.Scroll(up.cfg, pqueue) up.commitsList.Scroll(up.cfg, pqueue)
} }
func (up *userPage) Layout(cs layout.Constraints) (ui.Op, layout.Dimens) { func (up *userPage) Layout(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
l := up.commitsList l := up.commitsList
var ops ui.Ops
if l.Dragging() { if l.Dragging() {
ops = append(ops, key.OpHideInput{}) key.OpHideInput{}.Add(ops)
} }
select { select {
case commits := <-up.commitsResult: case commits := <-up.commitsResult:
up.commits = commits up.commits = commits
default: default:
} }
for i, ok := l.Init(cs, len(up.commits)); ok; i, ok = l.Index() { for i, ok := l.Init(ops, cs, len(up.commits)); ok; i, ok = l.Index() {
l.Elem(up.commit(i)) l.Elem(up.commit(i))
} }
op, dims := l.Layout() dims := l.Layout()
return append(ops, op), dims return dims
} }
func (up *userPage) commit(index int) layout.Widget { func (up *userPage) commit(index int) layout.Widget {
@@ -414,9 +414,9 @@ func (up *userPage) commit(index int) layout.Widget {
label := text.Label{Src: textColor, Face: up.faces.For(fonts.regular, ui.Sp(12)), Text: msg} label := text.Label{Src: textColor, Face: up.faces.For(fonts.regular, ui.Sp(12)), Text: msg}
return layout.Margin(c, return layout.Margin(c,
layout.Margins{Top: ui.Dp(16), Right: ui.Dp(8), Left: ui.Dp(8)}, layout.Margins{Top: ui.Dp(16), Right: ui.Dp(8), Left: ui.Dp(8)},
layout.F(func(cs layout.Constraints) (ui.Op, layout.Dimens) { layout.F(func(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
return (&layout.Flex{Axis: layout.Horizontal, MainAxisAlignment: layout.Start, CrossAxisAlignment: layout.Start}). return (&layout.Flex{Axis: layout.Horizontal, MainAxisAlignment: layout.Start, CrossAxisAlignment: layout.Start}).
Init(cs). Init(ops, cs).
Rigid(avatar). Rigid(avatar).
Flexible(-1, 1, layout.Fit, layout.Margin(c, layout.Margins{Left: ui.Dp(8)}, label)). Flexible(-1, 1, layout.Fit, layout.Margin(c, layout.Margins{Left: ui.Dp(8)}, label)).
Layout() Layout()
@@ -446,10 +446,10 @@ func (up *userPage) fetchCommits() {
}() }()
} }
func (a *App) layoutUsers(cs layout.Constraints) (ui.Op, layout.Dimens) { func (a *App) layoutUsers(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
c := a.cfg c := a.cfg
a.fab.Update(c, a.pqueue) a.fab.Update(c, a.pqueue)
st := (&layout.Stack{Alignment: layout.Center}).Init(cs). st := (&layout.Stack{Alignment: layout.Center}).Init(ops, cs).
Rigid(layout.Align( Rigid(layout.Align(
layout.SE, layout.SE,
layout.Margin(c, layout.Margin(c,
@@ -459,8 +459,8 @@ func (a *App) layoutUsers(cs layout.Constraints) (ui.Op, layout.Dimens) {
)) ))
a.edit.Update(c, a.pqueue, a.kqueue) a.edit.Update(c, a.pqueue, a.kqueue)
a.edit2.Update(c, a.pqueue, a.kqueue) a.edit2.Update(c, a.pqueue, a.kqueue)
return st.Expand(0, layout.F(func(cs layout.Constraints) (ui.Op, layout.Dimens) { return st.Expand(0, layout.F(func(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
return (&layout.Flex{Axis: layout.Vertical, MainAxisAlignment: layout.Start, CrossAxisAlignment: layout.Stretch}).Init(cs). return (&layout.Flex{Axis: layout.Vertical, MainAxisAlignment: layout.Start, CrossAxisAlignment: layout.Stretch}).Init(ops, cs).
Rigid(layout.Margin(c, Rigid(layout.Margin(c,
layout.EqualMargins(ui.Dp(16)), layout.EqualMargins(ui.Dp(16)),
layout.Sized(c, ui.Dp(0), ui.Dp(200), a.edit), layout.Sized(c, ui.Dp(0), ui.Dp(200), a.edit),
@@ -469,8 +469,8 @@ func (a *App) layoutUsers(cs layout.Constraints) (ui.Op, layout.Dimens) {
layout.Margins{Bottom: ui.Dp(16), Left: ui.Dp(16), Right: ui.Dp(16)}, layout.Margins{Bottom: ui.Dp(16), Left: ui.Dp(16), Right: ui.Dp(16)},
a.edit2, a.edit2,
)). )).
Rigid(layout.F(func(cs layout.Constraints) (ui.Op, layout.Dimens) { Rigid(layout.F(func(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
return (&layout.Stack{Alignment: layout.Center}).Init(cs). return (&layout.Stack{Alignment: layout.Center}).Init(ops, cs).
Rigid(layout.Margin(c, Rigid(layout.Margin(c,
layout.Margins{Top: ui.Dp(16), Right: ui.Dp(8), Bottom: ui.Dp(8), Left: ui.Dp(8)}, layout.Margins{Top: ui.Dp(16), Right: ui.Dp(8), Bottom: ui.Dp(8), Left: ui.Dp(8)},
text.Label{Src: rgb(0x888888), Face: a.face(fonts.regular, 9), Text: "GOPHERS"}, text.Label{Src: rgb(0x888888), Face: a.face(fonts.regular, 9), Text: "GOPHERS"},
@@ -490,35 +490,34 @@ func (a *ActionButton) Update(c *ui.Config, q pointer.Events) {
a.btnClicker.Update(q) a.btnClicker.Update(q)
} }
func (a *ActionButton) Layout(cs layout.Constraints) (ui.Op, layout.Dimens) { func (a *ActionButton) Layout(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
c := a.cfg c := a.cfg
fl := (&layout.Flex{Axis: layout.Vertical, MainAxisAlignment: layout.Start, CrossAxisAlignment: layout.End, MainAxisSize: layout.Min}).Init(cs) fl := (&layout.Flex{Axis: layout.Vertical, MainAxisAlignment: layout.Start, CrossAxisAlignment: layout.End, MainAxisSize: layout.Min}).Init(ops, cs)
fabCol := brandColor fabCol := brandColor
fl.Rigid(layout.Margin(c, fl.Rigid(layout.Margin(c,
layout.Margins{Top: ui.Dp(4)}, layout.Margins{Top: ui.Dp(4)},
layout.F(func(cs layout.Constraints) (ui.Op, layout.Dimens) { layout.F(func(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
op, dims := fab(c, a.sendIco.image(c), fabCol, ui.Dp(56)).Layout(cs) dims := fab(c, a.sendIco.image(c), fabCol, ui.Dp(56)).Layout(ops, cs)
ops := ui.Ops{op, a.btnClicker.Op(&gesture.Ellipse{dims.Size})} a.btnClicker.Op(ops, &gesture.Ellipse{dims.Size})
return ops, dims return dims
}), }),
)) ))
return fl.Layout() return fl.Layout()
} }
func (a *App) layoutContributors() layout.Widget { func (a *App) layoutContributors() layout.Widget {
return layout.F(func(cs layout.Constraints) (ui.Op, layout.Dimens) { return layout.F(func(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
c := a.cfg c := a.cfg
l := a.usersList l := a.usersList
l.Scroll(c, a.pqueue) l.Scroll(c, a.pqueue)
var ops ui.Ops
if l.Dragging() { if l.Dragging() {
ops = append(ops, key.OpHideInput{}) key.OpHideInput{}.Add(ops)
} }
for i, ok := l.Init(cs, len(a.users)); ok; i, ok = l.Index() { for i, ok := l.Init(ops, cs, len(a.users)); ok; i, ok = l.Index() {
l.Elem(a.user(c, i)) l.Elem(a.user(c, i))
} }
op, dims := l.Layout() dims := l.Layout()
return append(ops, op), dims return dims
}) })
} }
@@ -532,13 +531,13 @@ func (a *App) user(c *ui.Config, index int) layout.Widget {
} }
} }
avatar := clipCircle(layout.Sized(a.cfg, sz, sz, widget.Image{Src: u.avatar, Rect: u.avatar.Bounds()})) avatar := clipCircle(layout.Sized(a.cfg, sz, sz, widget.Image{Src: u.avatar, Rect: u.avatar.Bounds()}))
return layout.F(func(cs layout.Constraints) (ui.Op, layout.Dimens) { return layout.F(func(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
elem := (&layout.Flex{Axis: layout.Vertical, MainAxisAlignment: layout.Start, CrossAxisAlignment: layout.Start}).Init(cs) elem := (&layout.Flex{Axis: layout.Vertical, MainAxisAlignment: layout.Start, CrossAxisAlignment: layout.Start}).Init(ops, cs)
elem.Rigid(layout.F(func(cs layout.Constraints) (ui.Op, layout.Dimens) { elem.Rigid(layout.F(func(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
op, dims := layout.Margin(c, dims := layout.Margin(c,
layout.EqualMargins(ui.Dp(8)), layout.EqualMargins(ui.Dp(8)),
layout.F(func(cs layout.Constraints) (ui.Op, layout.Dimens) { layout.F(func(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
return centerRowOpts().Init(cs). return centerRowOpts().Init(ops, cs).
Rigid(layout.Margin(c, layout.Margins{Right: ui.Dp(8)}, avatar)). Rigid(layout.Margin(c, layout.Margins{Right: ui.Dp(8)}, avatar)).
Rigid(column( Rigid(column(
baseline( baseline(
@@ -555,9 +554,9 @@ func (a *App) user(c *ui.Config, index int) layout.Widget {
)). )).
Layout() Layout()
}), }),
).Layout(cs) ).Layout(ops, cs)
ops := ui.Ops{op, click.Op(&gesture.Rect{dims.Size})} click.Op(ops, &gesture.Rect{dims.Size})
return ops, dims return dims
})) }))
return elem.Layout() return elem.Layout()
}) })
@@ -588,8 +587,8 @@ func baseline(widgets ...layout.Widget) layout.Widget {
} }
func flex(f *layout.Flex, widgets ...layout.Widget) layout.Widget { func flex(f *layout.Flex, widgets ...layout.Widget) layout.Widget {
return layout.F(func(cs layout.Constraints) (ui.Op, layout.Dimens) { return layout.F(func(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
f.Init(cs) f.Init(ops, cs)
for _, w := range widgets { for _, w := range widgets {
f.Rigid(w) f.Rigid(w)
} }
@@ -598,41 +597,39 @@ func flex(f *layout.Flex, widgets ...layout.Widget) layout.Widget {
} }
func clipCircle(w layout.Widget) layout.Widget { func clipCircle(w layout.Widget) layout.Widget {
return layout.F(func(cs layout.Constraints) (ui.Op, layout.Dimens) { return layout.F(func(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
op, dims := w.Layout(cs) ops.Begin()
dims := w.Layout(ops, cs)
block := ops.End()
max := dims.Size.X max := dims.Size.X
if dy := dims.Size.Y; dy > max { if dy := dims.Size.Y; dy > max {
max = dy max = dy
} }
szf := float32(max) szf := float32(max)
rr := szf * .5 rr := szf * .5
op = gdraw.OpClip{ ops.Begin()
Path: rrect(szf, szf, rr, rr, rr, rr), gdraw.OpClip{Path: rrect(szf, szf, rr, rr, rr, rr)}.Add(ops)
Op: op, block.Add(ops)
} ops.End().Add(ops)
return op, dims return dims
}) })
} }
func fab(c *ui.Config, ico, col image.Image, size ui.Value) layout.Widget { func fab(c *ui.Config, ico, col image.Image, size ui.Value) layout.Widget {
return layout.F(func(cs layout.Constraints) (ui.Op, layout.Dimens) { return layout.F(func(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
szf := c.Pixels(size) szf := c.Pixels(size)
sz := int(szf + .5) sz := int(szf + .5)
rr := szf * .5 rr := szf * .5
dp := image.Point{X: (sz - ico.Bounds().Dx()) / 2, Y: (sz - ico.Bounds().Dy()) / 2} dp := image.Point{X: (sz - ico.Bounds().Dx()) / 2, Y: (sz - ico.Bounds().Dy()) / 2}
dims := image.Point{X: sz, Y: sz} dims := image.Point{X: sz, Y: sz}
op := gdraw.OpClip{ gdraw.OpClip{Path: rrect(szf, szf, rr, rr, rr, rr)}.Add(ops)
Path: rrect(szf, szf, rr, rr, rr, rr), gdraw.OpImage{Rect: f32.Rectangle{Max: f32.Point{X: float32(sz), Y: float32(sz)}}, Src: col, SrcRect: col.Bounds()}.Add(ops)
Op: ui.Ops{ gdraw.OpImage{
gdraw.OpImage{Rect: f32.Rectangle{Max: f32.Point{X: float32(sz), Y: float32(sz)}}, Src: col, SrcRect: col.Bounds()}, Rect: toRectF(ico.Bounds().Add(dp)),
gdraw.OpImage{ Src: ico,
Rect: toRectF(ico.Bounds().Add(dp)), SrcRect: ico.Bounds(),
Src: ico, }.Add(ops)
SrcRect: ico.Bounds(), return layout.Dimens{Size: dims}
},
},
}
return op, layout.Dimens{Size: dims}
}) })
} }
+4 -2
View File
@@ -46,14 +46,16 @@ func loop(w *app.Window) {
maroon := &image.Uniform{color.RGBA{127, 0, 0, 255}} maroon := &image.Uniform{color.RGBA{127, 0, 0, 255}}
face := faces.For(regular, ui.Sp(72)) face := faces.For(regular, ui.Sp(72))
message := "Hello, Gio" message := "Hello, Gio"
ops := new(ui.Ops)
for w.IsAlive() { for w.IsAlive() {
e := <-w.Events() e := <-w.Events()
switch e := e.(type) { switch e := e.(type) {
case app.Draw: case app.Draw:
faces.Cfg = e.Config faces.Cfg = e.Config
cs := layout.ExactConstraints(w.Size()) cs := layout.ExactConstraints(w.Size())
root, _ := (text.Label{Src: maroon, Face: face, Alignment: text.Center, Text: message}).Layout(cs) ops.Reset()
w.Draw(root) (text.Label{Src: maroon, Face: face, Alignment: text.Center, Text: message}).Layout(ops, cs)
w.Draw(ops)
faces.Frame() faces.Frame()
} }
} }
+101 -75
View File
@@ -14,6 +14,7 @@ import (
"gioui.org/ui/app/internal/gl" "gioui.org/ui/app/internal/gl"
gdraw "gioui.org/ui/draw" gdraw "gioui.org/ui/draw"
"gioui.org/ui/f32" "gioui.org/ui/f32"
"gioui.org/ui/internal/ops"
"gioui.org/ui/internal/path" "gioui.org/ui/internal/path"
"golang.org/x/image/draw" "golang.org/x/image/draw"
) )
@@ -31,13 +32,13 @@ type GPU struct {
refreshErr chan error refreshErr chan error
stop chan struct{} stop chan struct{}
stopped chan struct{} stopped chan struct{}
ops ops ops drawOps
} }
type frame struct { type frame struct {
collectStats bool collectStats bool
viewport image.Point viewport image.Point
ops ops ops drawOps
} }
type frameResult struct { type frameResult struct {
@@ -54,7 +55,8 @@ type renderer struct {
intersections packer intersections packer
} }
type ops struct { type drawOps struct {
reader ops.Reader
cache *resourceCache cache *resourceCache
viewport image.Point viewport image.Point
clearColor [3]float32 clearColor [3]float32
@@ -66,6 +68,14 @@ type ops struct {
pathOps []*pathOp pathOps []*pathOp
} }
type drawState struct {
clip f32.Rectangle
t ui.Transform
cpath *pathOp
rect bool
z int
}
type pathOp struct { type pathOp struct {
off f32.Point off f32.Point
// clip is the union of all // clip is the union of all
@@ -298,12 +308,13 @@ func (g *GPU) Refresh() {
g.setErr(<-g.refreshErr) g.setErr(<-g.refreshErr)
} }
func (g *GPU) Draw(profile bool, viewport image.Point, op ui.Op) { func (g *GPU) Draw(profile bool, viewport image.Point, root *ui.Ops) {
if g.err != nil { if g.err != nil {
return return
} }
g.Flush() g.Flush()
g.ops.collect(g.cache, op, viewport) g.ops.reset(g.cache, viewport)
g.ops.collect(g.cache, root, viewport)
g.frames <- frame{profile, viewport, g.ops} g.frames <- frame{profile, viewport, g.ops}
g.drawing = true g.drawing = true
} }
@@ -629,84 +640,99 @@ func floor(v float32) int {
} }
} }
func (ops *ops) collect(cache *resourceCache, op ui.Op, viewport image.Point) { func (d *drawOps) reset(cache *resourceCache, viewport image.Point) {
ops.clearColor = [3]float32{1.0, 1.0, 1.0} d.clearColor = [3]float32{1.0, 1.0, 1.0}
ops.cache = cache d.cache = cache
ops.viewport = viewport d.viewport = viewport
ops.imageOps = ops.imageOps[:0] d.imageOps = d.imageOps[:0]
ops.zimageOps = ops.zimageOps[:0] d.zimageOps = d.zimageOps[:0]
ops.pathOps = ops.pathOps[:0] d.pathOps = d.pathOps[:0]
}
func (d *drawOps) collect(cache *resourceCache, root *ui.Ops, viewport image.Point) {
d.reset(cache, viewport)
clip := f32.Rectangle{ clip := f32.Rectangle{
Max: f32.Point{X: float32(viewport.X), Y: float32(viewport.Y)}, Max: f32.Point{X: float32(viewport.X), Y: float32(viewport.Y)},
} }
ops.collectOp(op, clip, ui.Transform{}, nil, true, 0) d.reader.Reset(root.Data(), root.Refs())
d.collectOps(&d.reader, clip, ui.Transform{}, nil, true, 0)
} }
func (ops *ops) collectOp(op ui.Op, clip f32.Rectangle, t ui.Transform, cpath *pathOp, rect bool, z int) int { func (d *drawOps) collectOps(r *ops.Reader, clip f32.Rectangle, t ui.Transform, cpath *pathOp, rect bool, z int) int {
type childOp interface { loop:
ChildOp() ui.Op for {
} data, ok := r.Decode()
switch op := op.(type) { if !ok {
case ui.OpTransform:
t := t.Mul(op.Transform)
z = ops.collectOp(op.ChildOp(), clip, t, cpath, rect, z)
case gdraw.OpClip:
data := op.Path.Data().(*path.Path)
off := t.Transform(f32.Point{})
clip := clip.Intersect(data.Bounds.Add(off))
if clip.Empty() {
break break
} }
cpath := &pathOp{ switch ops.OpType(data[0]) {
parent: cpath, case ops.TypeTransform:
off: off, var op ui.OpTransform
op.Decode(data)
t = t.Mul(op.Transform)
case ops.TypeClip:
var op gdraw.OpClip
op.Decode(data, r.Refs)
if op.Path == nil {
clip = f32.Rectangle{}
continue
}
data := op.Path.Data().(*path.Path)
off := t.Transform(f32.Point{})
clip = clip.Intersect(data.Bounds.Add(off))
if clip.Empty() {
continue
}
cpath = &pathOp{
parent: cpath,
off: off,
}
if len(data.Vertices) > 0 {
rect = false
cpath.path = data
d.pathOps = append(d.pathOps, cpath)
}
case ops.TypeImage:
var op gdraw.OpImage
op.Decode(data, r.Refs)
off := t.Transform(f32.Point{})
clip := clip.Intersect(op.Rect.Add(off))
if clip.Empty() {
continue
}
bounds := boundRectF(clip)
mat := materialFor(d.cache, op, off, bounds)
if bounds.Min == (image.Point{}) && bounds.Max == d.viewport && mat.opaque && mat.material == materialColor {
// The image is a uniform opaque color and takes up the whole screen.
// Scrap images up to and including this image and set clear color.
d.zimageOps = d.zimageOps[:0]
d.imageOps = d.imageOps[:0]
z = 0
copy(d.clearColor[:], mat.color[:3])
continue
}
z++
// Assume 16-bit depth buffer.
const zdepth = 1 << 16
// Convert z to window-space, assuming depth range [0;1].
zf := float32(z)*2/zdepth - 1.0
img := imageOp{
z: zf,
path: cpath,
off: off,
clip: bounds,
material: mat,
}
if rect && img.material.opaque {
d.zimageOps = append(d.zimageOps, img)
} else {
d.imageOps = append(d.imageOps, img)
}
case ops.TypePush:
z = d.collectOps(r, clip, t, cpath, rect, z)
case ops.TypePop:
break loop
} }
if len(data.Vertices) > 0 {
rect = false
cpath.path = data
ops.pathOps = append(ops.pathOps, cpath)
}
z = ops.collectOp(op.ChildOp(), clip, t, cpath, rect, z)
case gdraw.OpImage:
off := t.Transform(f32.Point{})
clip := clip.Intersect(op.Rect.Add(off))
if clip.Empty() {
break
}
bounds := boundRectF(clip)
mat := materialFor(ops.cache, op, off, bounds)
if bounds.Min == (image.Point{}) && bounds.Max == ops.viewport && mat.opaque && mat.material == materialColor {
// The image is a uniform opaque color and takes up the whole screen.
// Scrap images up to and including this image and set clear color.
ops.zimageOps = ops.zimageOps[:0]
ops.imageOps = ops.imageOps[:0]
z = 0
copy(ops.clearColor[:], mat.color[:3])
break
}
z++
// Assume 16-bit depth buffer.
const zdepth = 1 << 16
// Convert z to window-space, assuming depth range [0;1].
zf := float32(z)*2/zdepth - 1.0
img := imageOp{
z: zf,
path: cpath,
off: off,
clip: bounds,
material: mat,
}
if rect && img.material.opaque {
ops.zimageOps = append(ops.zimageOps, img)
} else {
ops.imageOps = append(ops.imageOps, img)
}
case ui.Ops:
for _, op := range op {
z = ops.collectOp(op, clip, t, cpath, rect, z)
}
case childOp:
z = ops.collectOp(op.ChildOp(), clip, t, cpath, rect, z)
} }
return z return z
} }
+27 -28
View File
@@ -11,6 +11,7 @@ import (
"gioui.org/ui" "gioui.org/ui"
"gioui.org/ui/app/internal/gpu" "gioui.org/ui/app/internal/gpu"
"gioui.org/ui/internal/ops"
"gioui.org/ui/key" "gioui.org/ui/key"
"gioui.org/ui/pointer" "gioui.org/ui/pointer"
) )
@@ -41,6 +42,8 @@ type Window struct {
hasNextFrame bool hasNextFrame bool
nextFrame time.Time nextFrame time.Time
delayedDraw *time.Timer delayedDraw *time.Timer
reader ops.Reader
} }
// driver is the interface for the platform implementation // driver is the interface for the platform implementation
@@ -89,7 +92,7 @@ func (w *Window) Err() error {
return w.err return w.err
} }
func (w *Window) Draw(root ui.Op) { func (w *Window) Draw(root *ui.Ops) {
if !w.IsAlive() { if !w.IsAlive() {
return return
} }
@@ -132,13 +135,35 @@ func (w *Window) Draw(root ui.Op) {
w.timings = fmt.Sprintf("t:%7s %s", frameDur, w.gpu.Timings()) w.timings = fmt.Sprintf("t:%7s %s", frameDur, w.gpu.Timings())
w.setNextFrame(time.Time{}) w.setNextFrame(time.Time{})
} }
if t, ok := collectRedraws(root); ok { w.reader.Reset(root.Data(), root.Refs())
if t, ok := collectRedraws(&w.reader); ok {
w.setNextFrame(t) w.setNextFrame(t)
} }
w.updateAnimation() w.updateAnimation()
w.gpu.Draw(w.Profiling, size, root) w.gpu.Draw(w.Profiling, size, root)
} }
func collectRedraws(r *ops.Reader) (time.Time, bool) {
var t time.Time
redraw := false
for {
data, ok := r.Decode()
if !ok {
break
}
switch ops.OpType(data[0]) {
case ops.TypeRedraw:
var op ui.OpRedraw
op.Decode(data)
if !redraw || op.At.Before(t) {
redraw = true
t = op.At
}
}
}
return t, redraw
}
func (w *Window) Redraw() { func (w *Window) Redraw() {
if !w.IsAlive() { if !w.IsAlive() {
return return
@@ -256,29 +281,3 @@ func (w *Window) event(e Event) {
close(w.events) close(w.events)
} }
} }
func collectRedraws(op ui.Op) (time.Time, bool) {
type childOp interface {
ChildOp() ui.Op
}
switch op := op.(type) {
case ui.Ops:
var earliest time.Time
var valid bool
for _, op := range op {
if t, ok := collectRedraws(op); ok {
if !valid || t.Before(earliest) {
valid = true
earliest = t
}
}
}
return earliest, valid
case ui.OpRedraw:
return op.At, true
case childOp:
return collectRedraws(op.ChildOp())
default:
return time.Time{}, false
}
}
+58 -9
View File
@@ -3,11 +3,13 @@
package draw package draw
import ( import (
"encoding/binary"
"image" "image"
"math" "math"
"gioui.org/ui" "gioui.org/ui"
"gioui.org/ui/f32" "gioui.org/ui/f32"
"gioui.org/ui/internal/ops"
"gioui.org/ui/internal/path" "gioui.org/ui/internal/path"
) )
@@ -17,18 +19,65 @@ type OpImage struct {
SrcRect image.Rectangle SrcRect image.Rectangle
} }
func (i OpImage) Add(o *ui.Ops) {
data := make([]byte, ops.TypeImageLen)
data[0] = byte(ops.TypeImage)
bo := binary.LittleEndian
ref := o.Ref(i.Src)
bo.PutUint32(data[1:], uint32(ref))
bo.PutUint32(data[5:], math.Float32bits(i.Rect.Min.X))
bo.PutUint32(data[9:], math.Float32bits(i.Rect.Min.Y))
bo.PutUint32(data[13:], math.Float32bits(i.Rect.Max.X))
bo.PutUint32(data[17:], math.Float32bits(i.Rect.Max.Y))
bo.PutUint32(data[21:], uint32(i.SrcRect.Min.X))
bo.PutUint32(data[25:], uint32(i.SrcRect.Min.Y))
bo.PutUint32(data[29:], uint32(i.SrcRect.Max.X))
bo.PutUint32(data[33:], uint32(i.SrcRect.Max.Y))
o.Write(data)
}
func (i *OpImage) Decode(d []byte, refs []interface{}) {
bo := binary.LittleEndian
if ops.OpType(d[0]) != ops.TypeImage {
panic("invalid op")
}
ref := int(bo.Uint32(d[1:]))
r := f32.Rectangle{
Min: f32.Point{
X: math.Float32frombits(bo.Uint32(d[5:])),
Y: math.Float32frombits(bo.Uint32(d[9:])),
},
Max: f32.Point{
X: math.Float32frombits(bo.Uint32(d[13:])),
Y: math.Float32frombits(bo.Uint32(d[17:])),
},
}
sr := image.Rectangle{
Min: image.Point{
X: int(bo.Uint32(d[21:])),
Y: int(bo.Uint32(d[25:])),
},
Max: image.Point{
X: int(bo.Uint32(d[29:])),
Y: int(bo.Uint32(d[33:])),
},
}
*i = OpImage{
Rect: r,
Src: refs[ref].(image.Image),
SrcRect: sr,
}
}
func (OpImage) ImplementsOp() {} func (OpImage) ImplementsOp() {}
// ClipRect returns a special case of OpClip // RectPath constructs a path corresponding to
// that clips to a pixel aligned rectangular area. // a pixel aligned rectangular area.
func ClipRect(r image.Rectangle, op ui.Op) OpClip { func RectPath(r image.Rectangle) *Path {
return OpClip{ return &Path{
Path: &Path{ data: &path.Path{
data: &path.Path{ Bounds: toRectF(r),
Bounds: toRectF(r),
},
}, },
Op: op,
} }
} }
+21 -4
View File
@@ -3,16 +3,17 @@
package draw package draw
import ( import (
"encoding/binary"
"math" "math"
"gioui.org/ui" "gioui.org/ui"
"gioui.org/ui/f32" "gioui.org/ui/f32"
"gioui.org/ui/internal/ops"
"gioui.org/ui/internal/path" "gioui.org/ui/internal/path"
) )
type OpClip struct { type OpClip struct {
Path *Path Path *Path
Op ui.Op
} }
type Path struct { type Path struct {
@@ -33,11 +34,25 @@ func (p *Path) Data() interface{} {
return p.data return p.data
} }
func (p OpClip) ChildOp() ui.Op { func (c OpClip) Add(o *ui.Ops) {
return p.Op data := make([]byte, ops.TypeClipLen)
data[0] = byte(ops.TypeClip)
bo := binary.LittleEndian
ref := o.Ref(c.Path)
bo.PutUint32(data[1:], uint32(ref))
o.Write(data)
} }
func (p OpClip) ImplementsOp() {} func (c *OpClip) Decode(d []byte, refs []interface{}) {
bo := binary.LittleEndian
if ops.OpType(d[0]) != ops.TypeClip {
panic("invalid op")
}
ref := int(bo.Uint32(d[1:]))
*c = OpClip{
Path: refs[ref].(*Path),
}
}
// MoveTo moves the pen to the given position. // MoveTo moves the pen to the given position.
func (p *PathBuilder) Move(to f32.Point) { func (p *PathBuilder) Move(to f32.Point) {
@@ -265,3 +280,5 @@ func (p *PathBuilder) Path() *Path {
} }
return data return data
} }
func (p OpClip) ImplementsOp() {}
+7 -6
View File
@@ -83,8 +83,9 @@ const (
thresholdVelocity = 1 thresholdVelocity = 1
) )
func (c *Click) Op(a pointer.Area) pointer.OpHandler { func (c *Click) Op(ops *ui.Ops, a pointer.Area) {
return pointer.OpHandler{Area: a, Key: c} op := pointer.OpHandler{Area: a, Key: c}
op.Add(ops)
} }
func (c *Click) Update(q pointer.Events) []ClickEvent { func (c *Click) Update(q pointer.Events) []ClickEvent {
@@ -115,12 +116,12 @@ func (c *Click) Update(q pointer.Events) []ClickEvent {
return events return events
} }
func (s *Scroll) Op(a pointer.Area) ui.Op { func (s *Scroll) Op(ops *ui.Ops, a pointer.Area) {
oph := pointer.OpHandler{Area: a, Key: s, Grab: s.grab} oph := pointer.OpHandler{Area: a, Key: s, Grab: s.grab}
if !s.flinger.Active() { oph.Add(ops)
return oph if s.flinger.Active() {
ui.OpRedraw{}.Add(ops)
} }
return ui.Ops{oph, ui.OpRedraw{}}
} }
func (s *Scroll) Stop() { func (s *Scroll) Stop() {
+112
View File
@@ -0,0 +1,112 @@
package ops
import (
"encoding/binary"
)
type Reader struct {
pc int
stack []block
Refs []interface{}
data []byte
pseudoOp [1]byte
}
type block struct {
retPC int
endPC int
}
type OpType byte
const (
TypeBlockDef OpType = iota
TypeBlock
TypeTransform
TypeLayer
TypeRedraw
TypeClip
TypeImage
TypePointerHandler
TypeKeyHandler
TypeHideInput
TypePush
TypePop
)
const (
TypeBlockDefLen = 1 + 4
TypeBlockLen = 1 + 4
TypeTransformLen = 1 + 4*2
TypeLayerLen = 1
TypeRedrawLen = 1 + 8
TypeClipLen = 1 + 4
TypeImageLen = 1 + 4 + 4*4 + 4*4
TypePointerHandlerLen = 1 + 4 + 4 + 1
TypeKeyHandlerLen = 1 + 4 + 1
TypeHideInputLen = 1
TypePushLen = 1
TypePopLen = 1
)
var typeLengths = [...]int{
TypeBlockDefLen,
TypeBlockLen,
TypeTransformLen,
TypeLayerLen,
TypeRedrawLen,
TypeClipLen,
TypeImageLen,
TypePointerHandlerLen,
TypeKeyHandlerLen,
TypeHideInputLen,
TypePushLen,
TypePopLen,
}
// Reset start reading from the op list.
func (r *Reader) Reset(data []byte, refs []interface{}) {
r.Refs = refs
r.data = data
r.stack = r.stack[:0]
r.pc = 0
}
func (r *Reader) Decode() ([]byte, bool) {
bo := binary.LittleEndian
for {
if r.pc == len(r.data) {
return nil, false
}
if len(r.stack) > 0 {
b := r.stack[len(r.stack)-1]
if r.pc == b.endPC {
r.pc = b.retPC
r.stack = r.stack[:len(r.stack)-1]
r.pseudoOp[0] = byte(TypePop)
return r.pseudoOp[:], true
}
}
t := OpType(r.data[r.pc])
n := typeLengths[t]
data := r.data[r.pc : r.pc+n]
switch t {
case TypeBlock:
blockIdx := int(bo.Uint32(data[1:]))
if OpType(r.data[blockIdx]) != TypeBlockDef {
panic("invalid block reference")
}
blockLen := int(bo.Uint32(r.data[blockIdx+1:]))
r.stack = append(r.stack, block{r.pc + n, blockIdx + blockLen})
r.pc = blockIdx + TypeBlockDefLen
r.pseudoOp[0] = byte(TypePush)
return r.pseudoOp[:], true
case TypeBlockDef:
r.pc += int(bo.Uint32(data[1:]))
continue
}
r.pc += n
return data, true
}
}
+36
View File
@@ -2,6 +2,13 @@
package key package key
import (
"encoding/binary"
"gioui.org/ui"
"gioui.org/ui/internal/ops"
)
type OpHandler struct { type OpHandler struct {
Key Key Key Key
Focus bool Focus bool
@@ -63,6 +70,35 @@ const (
NamePageDown = '⇟' NamePageDown = '⇟'
) )
func (h OpHandler) Add(o *ui.Ops) {
data := make([]byte, ops.TypeKeyHandlerLen)
data[0] = byte(ops.TypeKeyHandler)
bo := binary.LittleEndian
if h.Focus {
data[1] = 1
}
bo.PutUint32(data[2:], uint32(o.Ref(h.Key)))
o.Write(data)
}
func (h *OpHandler) Decode(d []byte, refs []interface{}) {
bo := binary.LittleEndian
if ops.OpType(d[0]) != ops.TypeKeyHandler {
panic("invalid op")
}
key := int(bo.Uint32(d[2:]))
*h = OpHandler{
Focus: d[1] != 0,
Key: refs[key].(Key),
}
}
func (h OpHideInput) Add(o *ui.Ops) {
data := make([]byte, ops.TypeHideInputLen)
data[0] = byte(ops.TypeHideInput)
o.Write(data)
}
func (OpHandler) ImplementsOp() {} func (OpHandler) ImplementsOp() {}
func (OpHideInput) ImplementsOp() {} func (OpHideInput) ImplementsOp() {}
+35 -28
View File
@@ -4,12 +4,14 @@ package key
import ( import (
"gioui.org/ui" "gioui.org/ui"
"gioui.org/ui/internal/ops"
) )
type Queue struct { type Queue struct {
focus Key focus Key
events []Event events []Event
handlers map[Key]bool handlers map[Key]bool
reader ops.Reader
} }
type listenerPriority uint8 type listenerPriority uint8
@@ -21,9 +23,10 @@ const (
priNewFocus priNewFocus
) )
func (q *Queue) Frame(op ui.Op) TextInputState { func (q *Queue) Frame(root *ui.Ops) TextInputState {
q.events = q.events[:0] q.events = q.events[:0]
f, pri, hide := resolveFocus(op, q.focus) q.reader.Reset(root.Data(), root.Refs())
f, pri, hide := resolveFocus(&q.reader, q.focus)
changed := f != nil && f != q.focus changed := f != nil && f != q.focus
for k, active := range q.handlers { for k, active := range q.handlers {
if !active || changed { if !active || changed {
@@ -71,39 +74,43 @@ func (q *Queue) For(k Key) []Event {
return q.events return q.events
} }
func resolveFocus(op ui.Op, focus Key) (Key, listenerPriority, bool) { func resolveFocus(r *ops.Reader, focus Key) (Key, listenerPriority, bool) {
type childOp interface {
ChildOp() ui.Op
}
var k Key var k Key
var pri listenerPriority var pri listenerPriority
var hide bool var hide bool
switch op := op.(type) { loop:
case ui.Ops: for {
for i := len(op) - 1; i >= 0; i-- { data, ok := r.Decode()
newK, newPri, h := resolveFocus(op[i], focus) if !ok {
break
}
switch ops.OpType(data[0]) {
case ops.TypeKeyHandler:
var op OpHandler
op.Decode(data, r.Refs)
var newPri listenerPriority
switch {
case op.Focus:
newPri = priNewFocus
case op.Key == focus:
newPri = priCurrentFocus
default:
newPri = priDefault
}
if newPri >= pri {
k, pri = op.Key, newPri
}
case ops.TypeHideInput:
hide = true
case ops.TypePush:
newK, newPri, h := resolveFocus(r, focus)
hide = hide || h hide = hide || h
if newPri > pri { if newPri >= pri {
k, pri = newK, newPri k, pri = newK, newPri
} }
case ops.TypePop:
break loop
} }
case OpHandler:
var newPri listenerPriority
switch {
case op.Focus:
newPri = priNewFocus
case op.Key == focus:
newPri = priCurrentFocus
default:
newPri = priDefault
}
if newPri > pri {
k, pri = op.Key, newPri
}
case OpHideInput:
hide = true
case childOp:
return resolveFocus(op.ChildOp(), focus)
} }
return k, pri, hide return k, pri, hide
} }
+29 -23
View File
@@ -15,20 +15,20 @@ type Flex struct {
CrossAxisAlignment CrossAxisAlignment CrossAxisAlignment CrossAxisAlignment
MainAxisSize MainAxisSize MainAxisSize MainAxisSize
cs Constraints ops *ui.Ops
cs Constraints
children []flexChild children []flexChild
taken int taken int
maxCross int maxCross int
maxBaseline int maxBaseline int
ccache [10]flexChild ccache [10]flexChild
opCache [10]ui.Op
} }
type flexChild struct { type flexChild struct {
op ui.Op block ui.OpBlock
dims Dimens dims Dimens
} }
type MainAxisSize uint8 type MainAxisSize uint8
@@ -60,7 +60,8 @@ const (
Stretch Stretch
) )
func (f *Flex) Init(cs Constraints) *Flex { func (f *Flex) Init(ops *ui.Ops, cs Constraints) *Flex {
f.ops = ops
f.cs = cs f.cs = cs
if f.children == nil { if f.children == nil {
f.children = f.ccache[:0] f.children = f.ccache[:0]
@@ -78,7 +79,10 @@ func (f *Flex) Rigid(w Widget) *Flex {
mainMax -= f.taken mainMax -= f.taken
} }
cs := axisConstraints(f.Axis, Constraint{Max: mainMax}, f.crossConstraintChild(f.cs)) cs := axisConstraints(f.Axis, Constraint{Max: mainMax}, f.crossConstraintChild(f.cs))
op, dims := w.Layout(cs) f.ops.Begin()
ui.OpLayer{}.Add(f.ops)
dims := w.Layout(f.ops, cs)
block := f.ops.End()
f.taken += axisMain(f.Axis, dims.Size) f.taken += axisMain(f.Axis, dims.Size)
if c := axisCross(f.Axis, dims.Size); c > f.maxCross { if c := axisCross(f.Axis, dims.Size); c > f.maxCross {
f.maxCross = c f.maxCross = c
@@ -86,7 +90,7 @@ func (f *Flex) Rigid(w Widget) *Flex {
if b := dims.Baseline; b > f.maxBaseline { if b := dims.Baseline; b > f.maxBaseline {
f.maxBaseline = b f.maxBaseline = b
} }
f.children = append(f.children, flexChild{op, dims}) f.children = append(f.children, flexChild{block, dims})
return f return f
} }
@@ -101,7 +105,10 @@ func (f *Flex) Flexible(idx int, flex float32, mode FlexMode, w Widget) *Flex {
submainc.Min = submainc.Max submainc.Min = submainc.Max
} }
cs := axisConstraints(f.Axis, submainc, f.crossConstraintChild(f.cs)) cs := axisConstraints(f.Axis, submainc, f.crossConstraintChild(f.cs))
op, dims := w.Layout(cs) f.ops.Begin()
ui.OpLayer{}.Add(f.ops)
dims := w.Layout(f.ops, cs)
block := f.ops.End()
f.taken += axisMain(f.Axis, dims.Size) f.taken += axisMain(f.Axis, dims.Size)
if c := axisCross(f.Axis, dims.Size); c > f.maxCross { if c := axisCross(f.Axis, dims.Size); c > f.maxCross {
f.maxCross = c f.maxCross = c
@@ -109,15 +116,16 @@ func (f *Flex) Flexible(idx int, flex float32, mode FlexMode, w Widget) *Flex {
if b := dims.Baseline; b > f.maxBaseline { if b := dims.Baseline; b > f.maxBaseline {
f.maxBaseline = b f.maxBaseline = b
} }
f.children = append(f.children, flexChild{op, dims})
if idx < 0 { if idx < 0 {
idx += len(f.children) idx += len(f.children) + 1
} }
f.children[idx], f.children[len(f.children)-1] = f.children[len(f.children)-1], f.children[idx] f.children = append(f.children, flexChild{})
copy(f.children[idx+1:], f.children[idx:])
f.children[idx] = flexChild{block, dims}
return f return f
} }
func (f *Flex) Layout() (ui.Op, Dimens) { func (f *Flex) Layout() Dimens {
mainc := axisMainConstraint(f.Axis, f.cs) mainc := axisMainConstraint(f.Axis, f.cs)
crossSize := axisCrossConstraint(f.Axis, f.cs).Constrain(f.maxCross) crossSize := axisCrossConstraint(f.Axis, f.cs).Constrain(f.maxCross)
var space int var space int
@@ -140,13 +148,7 @@ func (f *Flex) Layout() (ui.Op, Dimens) {
case SpaceAround: case SpaceAround:
mainSize += space / (len(f.children) * 2) mainSize += space / (len(f.children) * 2)
} }
var ops ui.Ops for _, child := range f.children {
if len(f.children) > len(f.opCache) {
ops = make([]ui.Op, len(f.children))
} else {
ops = f.opCache[:len(f.children)]
}
for i, child := range f.children {
dims := child.dims dims := child.dims
b := dims.Baseline b := dims.Baseline
var cross int var cross int
@@ -160,8 +162,12 @@ func (f *Flex) Layout() (ui.Op, Dimens) {
cross = f.maxBaseline - b cross = f.maxBaseline - b
} }
} }
off := ui.Offset(toPointF(axisPoint(f.Axis, mainSize, cross))) f.ops.Begin()
ops[i] = ui.OpLayer{Op: ui.OpTransform{Transform: off, Op: child.op}} ui.OpTransform{
Transform: ui.Offset(toPointF(axisPoint(f.Axis, mainSize, cross))),
}.Add(f.ops)
child.block.Add(f.ops)
f.ops.End().Add(f.ops)
mainSize += axisMain(f.Axis, dims.Size) mainSize += axisMain(f.Axis, dims.Size)
switch f.MainAxisAlignment { switch f.MainAxisAlignment {
case SpaceEvenly: case SpaceEvenly:
@@ -187,7 +193,7 @@ func (f *Flex) Layout() (ui.Op, Dimens) {
if baseline == 0 { if baseline == 0 {
baseline = sz.Y baseline = sz.Y
} }
return ops, Dimens{Size: sz, Baseline: baseline} return Dimens{Size: sz, Baseline: baseline}
} }
func axisPoint(a Axis, main, cross int) image.Point { func axisPoint(a Axis, main, cross int) image.Point {
+36 -24
View File
@@ -12,8 +12,8 @@ import (
) )
type scrollChild struct { type scrollChild struct {
op ui.Op size image.Point
size image.Point block ui.OpBlock
} }
type List struct { type List struct {
@@ -24,12 +24,14 @@ type List struct {
// The distance scrolled since last call to Init. // The distance scrolled since last call to Init.
Distance int Distance int
area gesture.Rect
scroll gesture.Scroll scroll gesture.Scroll
scrollDir int scrollDir int
offset int offset int
first int first int
ops *ui.Ops
cs Constraints cs Constraints
len int len int
@@ -38,7 +40,6 @@ type List struct {
elem func(w Widget) elem func(w Widget)
size image.Point size image.Point
ops ui.Ops
} }
type Interface interface { type Interface interface {
@@ -46,19 +47,20 @@ type Interface interface {
At(i int) Widget At(i int) Widget
} }
func (l *List) Init(cs Constraints, len int) (int, bool) { func (l *List) Init(ops *ui.Ops, cs Constraints, len int) (int, bool) {
l.maxSize = 0 l.maxSize = 0
l.children = l.children[:0] l.children = l.children[:0]
l.ops = ops
l.cs = cs l.cs = cs
l.len = len l.len = len
l.elem = nil l.elem = nil
if l.first > len { if l.first > len {
l.first = len l.first = len
} }
l.ops = l.ops[:0]
if len == 0 { if len == 0 {
return 0, false return 0, false
} }
l.scroll.Op(ops, &l.area)
return l.Index() return l.Index()
} }
@@ -82,9 +84,9 @@ func (l *List) Index() (int, bool) {
return i, ok return i, ok
} }
func (l *List) Layout() (ui.Op, Dimens) { func (l *List) Layout() Dimens {
ops := append(ui.Ops{l.scroll.Op(&gesture.Rect{l.size})}, l.ops...) l.area.Size = l.size
return ops, Dimens{Size: l.size} return Dimens{Size: l.size}
} }
func (l *List) next() (int, bool) { func (l *List) next() (int, bool) {
@@ -116,21 +118,28 @@ func (l *List) Elem(w Widget) {
} }
func (l *List) backward(w Widget) { func (l *List) backward(w Widget) {
subcs := axisConstraints(l.Axis, Constraint{Max: ui.Inf}, l.crossConstraintChild(l.cs))
l.first-- l.first--
op, dims := w.Layout(subcs) child := l.add(w)
mainSize := axisMain(l.Axis, dims.Size) mainSize := axisMain(l.Axis, child.size)
l.offset += mainSize l.offset += mainSize
l.maxSize += mainSize l.maxSize += mainSize
l.children = append([]scrollChild{{op, dims.Size}}, l.children...) l.children = append([]scrollChild{child}, l.children...)
} }
func (l *List) forward(w Widget) { func (l *List) forward(w Widget) {
subcs := axisConstraints(l.Axis, Constraint{Max: ui.Inf}, l.crossConstraintChild(l.cs)) child := l.add(w)
op, dims := w.Layout(subcs) mainSize := axisMain(l.Axis, child.size)
mainSize := axisMain(l.Axis, dims.Size)
l.maxSize += mainSize l.maxSize += mainSize
l.children = append(l.children, scrollChild{op, dims.Size}) l.children = append(l.children, child)
}
func (l *List) add(w Widget) scrollChild {
subcs := axisConstraints(l.Axis, Constraint{Max: ui.Inf}, l.crossConstraintChild(l.cs))
l.ops.Begin()
ui.OpLayer{}.Add(l.ops)
dims := w.Layout(l.ops, subcs)
block := l.ops.End()
return scrollChild{dims.Size, block}
} }
func (l *List) draw() { func (l *List) draw() {
@@ -176,14 +185,17 @@ func (l *List) draw() {
if min < 0 { if min < 0 {
min = 0 min = 0
} }
op := draw.ClipRect( r := image.Rectangle{
image.Rectangle{ Min: axisPoint(l.Axis, min, -ui.Inf),
Min: axisPoint(l.Axis, min, -ui.Inf), Max: axisPoint(l.Axis, max, ui.Inf),
Max: axisPoint(l.Axis, max, ui.Inf), }
}, l.ops.Begin()
ui.OpTransform{Transform: ui.Offset(toPointF(axisPoint(l.Axis, pos, cross))), Op: child.op}, draw.OpClip{Path: draw.RectPath(r)}.Add(l.ops)
) ui.OpTransform{
l.ops = append(l.ops, ui.OpLayer{Op: op}) Transform: ui.Offset(toPointF(axisPoint(l.Axis, pos, cross))),
}.Add(l.ops)
child.block.Add(l.ops)
l.ops.End().Add(l.ops)
pos += axisMain(l.Axis, sz) pos += axisMain(l.Axis, sz)
} }
atStart := l.first == 0 && l.offset <= 0 atStart := l.first == 0 && l.offset <= 0
+25 -19
View File
@@ -10,7 +10,7 @@ import (
) )
type Widget interface { type Widget interface {
Layout(cs Constraints) (ui.Op, Dimens) Layout(ops *ui.Ops, cs Constraints) Dimens
} }
type Constraints struct { type Constraints struct {
@@ -29,7 +29,7 @@ type Dimens struct {
type Axis uint8 type Axis uint8
type F func(cs Constraints) (ui.Op, Dimens) type F func(ops *ui.Ops, cs Constraints) Dimens
const ( const (
Horizontal Axis = iota Horizontal Axis = iota
@@ -73,8 +73,8 @@ func ExactConstraints(size image.Point) Constraints {
} }
} }
func (f F) Layout(cs Constraints) (ui.Op, Dimens) { func (f F) Layout(ops *ui.Ops, cs Constraints) Dimens {
return f(cs) return f(ops, cs)
} }
type Margins struct { type Margins struct {
@@ -82,7 +82,7 @@ type Margins struct {
} }
func Margin(c *ui.Config, m Margins, w Widget) Widget { func Margin(c *ui.Config, m Margins, w Widget) Widget {
return F(func(cs Constraints) (ui.Op, Dimens) { return F(func(ops *ui.Ops, cs Constraints) Dimens {
mcs := cs mcs := cs
t, r, b, l := int(c.Pixels(m.Top)+0.5), int(c.Pixels(m.Right)+0.5), int(c.Pixels(m.Bottom)+0.5), int(c.Pixels(m.Left)+0.5) t, r, b, l := int(c.Pixels(m.Top)+0.5), int(c.Pixels(m.Right)+0.5), int(c.Pixels(m.Bottom)+0.5), int(c.Pixels(m.Left)+0.5)
if mcs.Width.Max != ui.Inf { if mcs.Width.Max != ui.Inf {
@@ -105,10 +105,11 @@ func Margin(c *ui.Config, m Margins, w Widget) Widget {
mcs.Height.Max = mcs.Height.Min mcs.Height.Max = mcs.Height.Min
} }
} }
ops.Begin()
op, dims := w.Layout(mcs) ui.OpTransform{Transform: ui.Offset(toPointF(image.Point{X: l, Y: t}))}.Add(ops)
op = ui.OpTransform{Transform: ui.Offset(toPointF(image.Point{X: l, Y: t})), Op: op} dims := w.Layout(ops, mcs)
return op, Dimens{ ops.End().Add(ops)
return Dimens{
Size: cs.Constrain(dims.Size.Add(image.Point{X: r + l, Y: t + b})), Size: cs.Constrain(dims.Size.Add(image.Point{X: r + l, Y: t + b})),
Baseline: dims.Baseline + t, Baseline: dims.Baseline + t,
} }
@@ -124,7 +125,7 @@ func isInf(v ui.Value) bool {
} }
func Capped(c *ui.Config, maxWidth, maxHeight ui.Value, wt Widget) Widget { func Capped(c *ui.Config, maxWidth, maxHeight ui.Value, wt Widget) Widget {
return F(func(cs Constraints) (ui.Op, Dimens) { return F(func(ops *ui.Ops, cs Constraints) Dimens {
if !isInf(maxWidth) { if !isInf(maxWidth) {
mw := int(c.Pixels(maxWidth) + .5) mw := int(c.Pixels(maxWidth) + .5)
if mw < cs.Width.Min { if mw < cs.Width.Min {
@@ -143,12 +144,12 @@ func Capped(c *ui.Config, maxWidth, maxHeight ui.Value, wt Widget) Widget {
cs.Height.Max = mh cs.Height.Max = mh
} }
} }
return wt.Layout(cs) return wt.Layout(ops, cs)
}) })
} }
func Sized(c *ui.Config, width, height ui.Value, wt Widget) Widget { func Sized(c *ui.Config, width, height ui.Value, wt Widget) Widget {
return F(func(cs Constraints) (ui.Op, Dimens) { return F(func(ops *ui.Ops, cs Constraints) Dimens {
if h := int(c.Pixels(height) + 0.5); h != 0 { if h := int(c.Pixels(height) + 0.5); h != 0 {
if cs.Height.Min < h { if cs.Height.Min < h {
cs.Height.Min = h cs.Height.Min = h
@@ -165,25 +166,27 @@ func Sized(c *ui.Config, width, height ui.Value, wt Widget) Widget {
cs.Width.Max = w cs.Width.Max = w
} }
} }
return wt.Layout(cs) return wt.Layout(ops, cs)
}) })
} }
func Expand(w Widget) Widget { func Expand(w Widget) Widget {
return F(func(cs Constraints) (ui.Op, Dimens) { return F(func(ops *ui.Ops, cs Constraints) Dimens {
if cs.Height.Max != ui.Inf { if cs.Height.Max != ui.Inf {
cs.Height.Min = cs.Height.Max cs.Height.Min = cs.Height.Max
} }
if cs.Width.Max != ui.Inf { if cs.Width.Max != ui.Inf {
cs.Width.Min = cs.Width.Max cs.Width.Min = cs.Width.Max
} }
return w.Layout(cs) return w.Layout(ops, cs)
}) })
} }
func Align(alignment Direction, w Widget) Widget { func Align(alignment Direction, w Widget) Widget {
return F(func(cs Constraints) (ui.Op, Dimens) { return F(func(ops *ui.Ops, cs Constraints) Dimens {
op, dims := w.Layout(cs.Loose()) ops.Begin()
dims := w.Layout(ops, cs.Loose())
block := ops.End()
sz := dims.Size sz := dims.Size
if cs.Width.Max != ui.Inf { if cs.Width.Max != ui.Inf {
sz.X = cs.Width.Max sz.X = cs.Width.Max
@@ -204,8 +207,11 @@ func Align(alignment Direction, w Widget) Widget {
case SW, S, SE: case SW, S, SE:
p.Y = sz.Y - dims.Size.Y p.Y = sz.Y - dims.Size.Y
} }
op = ui.OpTransform{Transform: ui.Offset(toPointF(p)), Op: op} ops.Begin()
return op, Dimens{ ui.OpTransform{Transform: ui.Offset(toPointF(p))}.Add(ops)
block.Add(ops)
ops.End().Add(ops)
return Dimens{
Size: sz, Size: sz,
Baseline: dims.Baseline, Baseline: dims.Baseline,
} }
+29 -22
View File
@@ -11,18 +11,18 @@ import (
type Stack struct { type Stack struct {
Alignment Direction Alignment Direction
ops *ui.Ops
cs Constraints cs Constraints
children []stackChild children []stackChild
maxSZ image.Point maxSZ image.Point
baseline int baseline int
ccache [10]stackChild ccache [10]stackChild
opCache [10]ui.Op
} }
type stackChild struct { type stackChild struct {
op ui.Op block ui.OpBlock
dims Dimens dims Dimens
} }
type Direction uint8 type Direction uint8
@@ -38,26 +38,31 @@ const (
W W
) )
func (s *Stack) Init(cs Constraints) *Stack { func (s *Stack) Init(ops *ui.Ops, cs Constraints) *Stack {
if s.children == nil { if s.children == nil {
s.children = s.ccache[:0] s.children = s.ccache[:0]
} }
s.children = s.children[:0] s.children = s.children[:0]
s.maxSZ = image.Point{} s.maxSZ = image.Point{}
s.baseline = 0 s.baseline = 0
s.ops = ops
s.cs = cs s.cs = cs
return s return s
} }
func (s *Stack) Rigid(w Widget) *Stack { func (s *Stack) Rigid(w Widget) *Stack {
op, dims := w.Layout(s.cs) s.ops.Begin()
ui.OpLayer{}.Add(s.ops)
dims := w.Layout(s.ops, s.cs)
b := s.ops.End()
if w := dims.Size.X; w > s.maxSZ.X { if w := dims.Size.X; w > s.maxSZ.X {
s.maxSZ.X = w s.maxSZ.X = w
} }
if h := dims.Size.Y; h > s.maxSZ.Y { if h := dims.Size.Y; h > s.maxSZ.Y {
s.maxSZ.Y = h s.maxSZ.Y = h
} }
s.add(op, dims) s.addjustBaseline(dims)
s.children = append(s.children, stackChild{b, dims})
return s return s
} }
@@ -66,16 +71,21 @@ func (s *Stack) Expand(idx int, w Widget) *Stack {
Width: Constraint{Min: s.maxSZ.X, Max: s.maxSZ.X}, Width: Constraint{Min: s.maxSZ.X, Max: s.maxSZ.X},
Height: Constraint{Min: s.maxSZ.Y, Max: s.maxSZ.Y}, Height: Constraint{Min: s.maxSZ.Y, Max: s.maxSZ.Y},
} }
s.add(w.Layout(cs)) s.ops.Begin()
ui.OpLayer{}.Add(s.ops)
dims := w.Layout(s.ops, cs)
b := s.ops.End()
s.addjustBaseline(dims)
if idx < 0 { if idx < 0 {
idx += len(s.children) idx += len(s.children) + 1
} }
s.children[idx], s.children[len(s.children)-1] = s.children[len(s.children)-1], s.children[idx] s.children = append(s.children, stackChild{})
copy(s.children[idx+1:], s.children[idx:])
s.children[idx] = stackChild{b, dims}
return s return s
} }
func (s *Stack) add(op ui.Op, dims Dimens) { func (s *Stack) addjustBaseline(dims Dimens) {
s.children = append(s.children, stackChild{op, dims})
if s.baseline == 0 { if s.baseline == 0 {
if b := dims.Baseline; b != dims.Size.Y { if b := dims.Baseline; b != dims.Size.Y {
s.baseline = b s.baseline = b
@@ -83,14 +93,8 @@ func (s *Stack) add(op ui.Op, dims Dimens) {
} }
} }
func (s *Stack) Layout() (ui.Op, Dimens) { func (s *Stack) Layout() Dimens {
var ops ui.Ops for _, ch := range s.children {
if len(s.children) > len(s.opCache) {
ops = make([]ui.Op, len(s.children))
} else {
ops = s.opCache[:len(s.children)]
}
for i, ch := range s.children {
sz := ch.dims.Size sz := ch.dims.Size
var p image.Point var p image.Point
switch s.Alignment { switch s.Alignment {
@@ -105,13 +109,16 @@ func (s *Stack) Layout() (ui.Op, Dimens) {
case SW, S, SE: case SW, S, SE:
p.Y = s.maxSZ.Y - sz.Y p.Y = s.maxSZ.Y - sz.Y
} }
ops[i] = ui.OpLayer{Op: ui.OpTransform{Transform: ui.Offset(toPointF(p)), Op: ch.op}} s.ops.Begin()
ui.OpTransform{Transform: ui.Offset(toPointF(p))}.Add(s.ops)
ch.block.Add(s.ops)
s.ops.End().Add(s.ops)
} }
b := s.baseline b := s.baseline
if b == 0 { if b == 0 {
b = s.maxSZ.Y b = s.maxSZ.Y
} }
return ops, Dimens{ return Dimens{
Size: s.maxSZ, Size: s.maxSZ,
Baseline: b, Baseline: b,
} }
+79
View File
@@ -0,0 +1,79 @@
package ui
import (
"encoding/binary"
"gioui.org/ui/internal/ops"
)
// Ops hold a list of serialized Ops.
type Ops struct {
// Stack of block start indices.
stack []int
// Serialized ops.
data []byte
// Op references.
refs []interface{}
}
type OpBlock struct {
idx int
}
// Begin a block of ops.
func (o *Ops) Begin() {
o.stack = append(o.stack, o.Size())
data := make([]byte, ops.TypeBlockDefLen)
data[0] = byte(ops.TypeBlockDef)
o.Write(data)
}
// End the most recent block and return
// an op for invoking the completed block.
func (o *Ops) End() OpBlock {
start := o.stack[len(o.stack)-1]
o.stack = o.stack[:len(o.stack)-1]
blockLen := o.Size() - start
bo := binary.LittleEndian
bo.PutUint32(o.data[start+1:], uint32(blockLen))
return OpBlock{start}
}
// Reset clears the Ops.
func (o *Ops) Reset() {
o.refs = o.refs[:0]
o.stack = o.stack[:0]
o.data = o.data[:0]
}
func (o *Ops) Refs() []interface{} {
return o.refs
}
func (o *Ops) Data() []byte {
return o.data
}
func (o *Ops) Ref(r interface{}) int {
o.refs = append(o.refs, r)
return len(o.refs) - 1
}
func (o *Ops) Write(op []byte) {
o.data = append(o.data, op...)
}
// Size returns the length of the serialized Op data.
func (o *Ops) Size() int {
return len(o.data)
}
func (b OpBlock) Add(o *Ops) {
data := make([]byte, ops.TypeBlockLen)
data[0] = byte(ops.TypeBlock)
bo := binary.LittleEndian
bo.PutUint32(data[1:], uint32(b.idx))
o.Write(data)
}
func (OpBlock) ImplementsOp() {}
+29
View File
@@ -3,9 +3,12 @@
package pointer package pointer
import ( import (
"encoding/binary"
"time" "time"
"gioui.org/ui"
"gioui.org/ui/f32" "gioui.org/ui/f32"
"gioui.org/ui/internal/ops"
) )
type Event struct { type Event struct {
@@ -66,6 +69,32 @@ const (
Grabbed Grabbed
) )
func (h OpHandler) Add(o *ui.Ops) {
data := make([]byte, ops.TypePointerHandlerLen)
data[0] = byte(ops.TypePointerHandler)
bo := binary.LittleEndian
if h.Grab {
data[1] = 1
}
bo.PutUint32(data[2:], uint32(o.Ref(h.Key)))
bo.PutUint32(data[6:], uint32(o.Ref(h.Area)))
o.Write(data)
}
func (h *OpHandler) Decode(d []byte, refs []interface{}) {
bo := binary.LittleEndian
if ops.OpType(d[0]) != ops.TypePointerHandler {
panic("invalid op")
}
key := int(bo.Uint32(d[2:]))
area := int(bo.Uint32(d[6:]))
*h = OpHandler{
Grab: d[1] != 0,
Key: refs[key].(Key),
Area: refs[area].(Area),
}
}
func (t Type) String() string { func (t Type) String() string {
switch t { switch t {
case Press: case Press:
+34 -43
View File
@@ -5,12 +5,14 @@ package pointer
import ( import (
"gioui.org/ui" "gioui.org/ui"
"gioui.org/ui/f32" "gioui.org/ui/f32"
"gioui.org/ui/internal/ops"
) )
type Queue struct { type Queue struct {
hitTree []hitNode hitTree []hitNode
handlers map[Key]*handler handlers map[Key]*handler
pointers []pointerInfo pointers []pointerInfo
reader ops.Reader
scratch []Key scratch []Key
} }
@@ -35,49 +37,37 @@ type handler struct {
wantsGrab bool wantsGrab bool
} }
type childOp interface { func (q *Queue) collectHandlers(r *ops.Reader, t ui.Transform, layer int) {
ChildOp() ui.Op for {
} data, ok := r.Decode()
func (q *Queue) collectHandlers(op ui.Op, t ui.Transform, layer int) ui.Op {
switch op := op.(type) {
case ui.Ops:
var all ui.Ops
for _, op := range op {
if op := q.collectHandlers(op, t, layer); op != nil {
if ops, ok := op.(ui.Ops); ok {
all = append(all, ops...)
} else {
all = append(all, op)
}
}
}
return all
case ui.OpLayer:
layer++
q.hitTree = append(q.hitTree, hitNode{level: layer})
child := q.collectHandlers(op.ChildOp(), t, layer)
if child == nil {
return nil
}
return ui.OpLayer{Op: child}
case ui.OpTransform:
return q.collectHandlers(op.ChildOp(), t.Mul(op.Transform), layer)
case OpHandler:
q.hitTree = append(q.hitTree, hitNode{level: layer, key: op.Key})
h, ok := q.handlers[op.Key]
if !ok { if !ok {
h = new(handler) return
q.handlers[op.Key] = h }
switch ops.OpType(data[0]) {
case ops.TypePush:
q.collectHandlers(r, t, layer)
case ops.TypePop:
return
case ops.TypeLayer:
layer++
q.hitTree = append(q.hitTree, hitNode{level: layer})
case ops.TypeTransform:
var op ui.OpTransform
op.Decode(data)
t = t.Mul(op.Transform)
case ops.TypePointerHandler:
var op OpHandler
op.Decode(data, r.Refs)
q.hitTree = append(q.hitTree, hitNode{level: layer, key: op.Key})
h, ok := q.handlers[op.Key]
if !ok {
h = new(handler)
q.handlers[op.Key] = h
}
h.area = op.Area
h.transform = t
h.wantsGrab = h.wantsGrab || op.Grab
} }
h.area = op.Area
h.transform = t
h.wantsGrab = h.wantsGrab || op.Grab
return op
case childOp:
return q.collectHandlers(op.ChildOp(), t, layer)
default:
return nil
} }
} }
@@ -115,7 +105,7 @@ func (q *Queue) init() {
} }
} }
func (q *Queue) Frame(op ui.Op) { func (q *Queue) Frame(root *ui.Ops) {
q.init() q.init()
for k, h := range q.handlers { for k, h := range q.handlers {
if !h.active { if !h.active {
@@ -126,7 +116,8 @@ func (q *Queue) Frame(op ui.Op) {
} }
} }
q.hitTree = q.hitTree[:0] q.hitTree = q.hitTree[:0]
q.collectHandlers(op, ui.Transform{}, 0) q.reader.Reset(root.Data(), root.Refs())
q.collectHandlers(&q.reader, ui.Transform{}, 0)
} }
func (q *Queue) For(k Key) []Event { func (q *Queue) For(k Key) []Event {
+14 -13
View File
@@ -48,8 +48,6 @@ type Editor struct {
scrollOff image.Point scrollOff image.Point
clicker gesture.Click clicker gesture.Click
ops ui.Ops
} }
type linePath struct { type linePath struct {
@@ -126,7 +124,7 @@ func (e *Editor) caretWidth() fixed.Int26_6 {
return fixed.Int26_6(oneDp * 64) return fixed.Int26_6(oneDp * 64)
} }
func (e *Editor) Layout(cs layout.Constraints) (ui.Op, layout.Dimens) { func (e *Editor) Layout(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
twoDp := int(e.cfg.Pixels(ui.Dp(2)) + 0.5) twoDp := int(e.cfg.Pixels(ui.Dp(2)) + 0.5)
e.padLeft, e.padRight = twoDp, twoDp e.padLeft, e.padRight = twoDp, twoDp
maxWidth := cs.Width.Max maxWidth := cs.Width.Max
@@ -155,8 +153,7 @@ func (e *Editor) Layout(cs layout.Constraints) (ui.Op, layout.Dimens) {
Min: image.Point{X: 0, Y: 0}, Min: image.Point{X: 0, Y: 0},
Max: image.Point{X: e.viewSize.X, Y: e.viewSize.Y}, Max: image.Point{X: e.viewSize.X, Y: e.viewSize.Y},
} }
e.ops = e.ops[:0] key.OpHandler{Key: e, Focus: e.requestFocus}.Add(ops)
e.ops = append(e.ops, key.OpHandler{Key: e, Focus: e.requestFocus})
e.requestFocus = false e.requestFocus = false
e.it = lineIterator{ e.it = lineIterator{
Lines: lines, Lines: lines,
@@ -171,10 +168,11 @@ func (e *Editor) Layout(cs layout.Constraints) (ui.Op, layout.Dimens) {
break break
} }
path := e.Face.Path(str) path := e.Face.Path(str)
e.ops = append(e.ops, ui.OpTransform{ ops.Begin()
Transform: ui.Offset(lineOff), ui.OpTransform{Transform: ui.Offset(lineOff)}.Add(ops)
Op: draw.OpClip{Path: path, Op: draw.OpImage{Rect: toRectF(clip).Sub(lineOff), Src: e.Src, SrcRect: e.Src.Bounds()}}, draw.OpClip{Path: path}.Add(ops)
}) draw.OpImage{Rect: toRectF(clip).Sub(lineOff), Src: e.Src, SrcRect: e.Src.Bounds()}.Add(ops)
ops.End().Add(ops)
} }
if e.focused { if e.focused {
now := e.cfg.Now now := e.cfg.Now
@@ -197,18 +195,21 @@ func (e *Editor) Layout(cs layout.Constraints) (ui.Op, layout.Dimens) {
}) })
carRect = clip.Intersect(carRect) carRect = clip.Intersect(carRect)
if !carRect.Empty() { if !carRect.Empty() {
e.ops = append(e.ops, draw.OpImage{Src: e.Src, Rect: toRectF(carRect), SrcRect: e.Src.Bounds()}) img := draw.OpImage{Src: e.Src, Rect: toRectF(carRect), SrcRect: e.Src.Bounds()}
img.Add(ops)
} }
} }
if blinking { if blinking {
e.ops = append(e.ops, ui.OpRedraw{At: nextBlink}) redraw := ui.OpRedraw{At: nextBlink}
redraw.Add(ops)
} }
} }
baseline := e.padTop + e.dims.Baseline baseline := e.padTop + e.dims.Baseline
area := &gesture.Rect{e.viewSize} area := &gesture.Rect{e.viewSize}
e.ops = append(e.ops, e.scroller.Op(area), e.clicker.Op(area)) e.scroller.Op(ops, area)
return e.ops, layout.Dimens{Size: e.viewSize, Baseline: baseline} e.clicker.Op(ops, area)
return layout.Dimens{Size: e.viewSize, Baseline: baseline}
} }
func (e *Editor) layout() { func (e *Editor) layout() {
+7 -8
View File
@@ -80,7 +80,7 @@ func (l *lineIterator) Next() (String, f32.Point, bool) {
return String{}, f32.Point{}, false return String{}, f32.Point{}, false
} }
func (l Label) Layout(cs layout.Constraints) (ui.Op, layout.Dimens) { func (l Label) Layout(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
textLayout := l.Face.Layout(l.Text, false, cs.Width.Max) textLayout := l.Face.Layout(l.Text, false, cs.Width.Max)
lines := textLayout.Lines lines := textLayout.Lines
dims := linesDimens(lines) dims := linesDimens(lines)
@@ -90,7 +90,6 @@ func (l Label) Layout(cs layout.Constraints) (ui.Op, layout.Dimens) {
Min: image.Point{X: -ui.Inf, Y: -padTop}, Min: image.Point{X: -ui.Inf, Y: -padTop},
Max: image.Point{X: ui.Inf, Y: dims.Size.Y + padBottom}, Max: image.Point{X: ui.Inf, Y: dims.Size.Y + padBottom},
} }
var ops ui.Ops = make([]ui.Op, len(lines))[:0]
l.it = lineIterator{ l.it = lineIterator{
Lines: lines, Lines: lines,
Clip: clip, Clip: clip,
@@ -104,13 +103,13 @@ func (l Label) Layout(cs layout.Constraints) (ui.Op, layout.Dimens) {
} }
path := l.Face.Path(str) path := l.Face.Path(str)
lclip := toRectF(clip).Sub(off) lclip := toRectF(clip).Sub(off)
op := ui.OpTransform{ ops.Begin()
Transform: ui.Offset(off), ui.OpTransform{Transform: ui.Offset(off)}.Add(ops)
Op: draw.OpClip{Path: path, Op: draw.OpImage{Rect: lclip, Src: l.Src, SrcRect: l.Src.Bounds()}}, draw.OpClip{Path: path}.Add(ops)
} draw.OpImage{Rect: lclip, Src: l.Src, SrcRect: l.Src.Bounds()}.Add(ops)
ops = append(ops, op) ops.End().Add(ops)
} }
return ops, dims return dims
} }
func itof(i int) float32 { func itof(i int) float32 {
+59 -11
View File
@@ -3,9 +3,12 @@
package ui package ui
import ( import (
"encoding/binary"
"math"
"time" "time"
"gioui.org/ui/f32" "gioui.org/ui/f32"
"gioui.org/ui/internal/ops"
) )
// Config contain the context for updating and // Config contain the context for updating and
@@ -33,7 +36,7 @@ func (c *Config) Pixels(v Value) float32 {
} }
} }
// Op is implemented by all known drawing and control // Op is implemented by all drawing and control
// operations. // operations.
type Op interface { type Op interface {
ImplementsOp() ImplementsOp()
@@ -41,7 +44,6 @@ type Op interface {
// OpLayer represents a semantic layer of UI. // OpLayer represents a semantic layer of UI.
type OpLayer struct { type OpLayer struct {
Op Op
} }
// OpRedraw requests a redraw at the given time. Use // OpRedraw requests a redraw at the given time. Use
@@ -50,13 +52,9 @@ type OpRedraw struct {
At time.Time At time.Time
} }
// Ops is the operation for a list of ops.
type Ops []Op
// OpTransform transforms an op. // OpTransform transforms an op.
type OpTransform struct { type OpTransform struct {
Transform Transform Transform Transform
Op Op
} }
type Transform struct { type Transform struct {
@@ -64,6 +62,30 @@ type Transform struct {
offset f32.Point offset f32.Point
} }
func (r OpRedraw) Add(o *Ops) {
data := make([]byte, ops.TypeRedrawLen)
data[0] = byte(ops.TypeRedraw)
bo := binary.LittleEndian
// UnixNano cannot represent the zero time.
if t := r.At; !t.IsZero() {
nanos := t.UnixNano()
if nanos > 0 {
bo.PutUint64(data[1:], uint64(nanos))
}
}
o.Write(data)
}
func (r *OpRedraw) Decode(d []byte) {
bo := binary.LittleEndian
if ops.OpType(d[0]) != ops.TypeRedraw {
panic("invalid op")
}
if nanos := bo.Uint64(d[1:]); nanos > 0 {
r.At = time.Unix(0, int64(nanos))
}
}
func (t Transform) InvTransform(p f32.Point) f32.Point { func (t Transform) InvTransform(p f32.Point) f32.Point {
return p.Sub(t.offset) return p.Sub(t.offset)
} }
@@ -78,12 +100,39 @@ func (t Transform) Mul(t2 Transform) Transform {
} }
} }
func (t OpTransform) ChildOp() Op { func (t OpTransform) Add(o *Ops) {
return t.Op data := make([]byte, ops.TypeTransformLen)
data[0] = byte(ops.TypeTransform)
bo := binary.LittleEndian
bo.PutUint32(data[1:], math.Float32bits(t.Transform.offset.X))
bo.PutUint32(data[5:], math.Float32bits(t.Transform.offset.Y))
o.Write(data)
} }
func (o OpLayer) ChildOp() Op { func (t *OpTransform) Decode(d []byte) {
return o.Op bo := binary.LittleEndian
if ops.OpType(d[0]) != ops.TypeTransform {
panic("invalid op")
}
*t = OpTransform{
Transform: Offset(f32.Point{
X: math.Float32frombits(bo.Uint32(d[1:])),
Y: math.Float32frombits(bo.Uint32(d[5:])),
}),
}
}
func (l OpLayer) Add(o *Ops) {
data := make([]byte, ops.TypeLayerLen)
data[0] = byte(ops.TypeLayer)
o.Write(data)
}
func (l *OpLayer) Decode(d []byte) {
if ops.OpType(d[0]) != ops.TypeLayer {
panic("invalid op")
}
*l = OpLayer{}
} }
func Offset(o f32.Point) Transform { func Offset(o f32.Point) Transform {
@@ -93,7 +142,6 @@ func Offset(o f32.Point) Transform {
// Inf is the int value that represents an unbounded maximum constraint. // Inf is the int value that represents an unbounded maximum constraint.
const Inf = int(^uint(0) >> 1) const Inf = int(^uint(0) >> 1)
func (Ops) ImplementsOp() {}
func (OpLayer) ImplementsOp() {} func (OpLayer) ImplementsOp() {}
func (OpTransform) ImplementsOp() {} func (OpTransform) ImplementsOp() {}
func (OpRedraw) ImplementsOp() {} func (OpRedraw) ImplementsOp() {}
+3 -3
View File
@@ -16,7 +16,7 @@ type Image struct {
Rect image.Rectangle Rect image.Rectangle
} }
func (im Image) Layout(cs layout.Constraints) (ui.Op, layout.Dimens) { func (im Image) Layout(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
d := image.Point{X: cs.Width.Max, Y: cs.Height.Max} d := image.Point{X: cs.Width.Max, Y: cs.Height.Max}
if d.X == ui.Inf { if d.X == ui.Inf {
d.X = cs.Width.Min d.X = cs.Width.Min
@@ -27,6 +27,6 @@ func (im Image) Layout(cs layout.Constraints) (ui.Op, layout.Dimens) {
dr := f32.Rectangle{ dr := f32.Rectangle{
Max: f32.Point{X: float32(d.X), Y: float32(d.Y)}, Max: f32.Point{X: float32(d.X), Y: float32(d.Y)},
} }
op := draw.OpImage{Rect: dr, Src: im.Src, SrcRect: im.Rect} draw.OpImage{Rect: dr, Src: im.Src, SrcRect: im.Rect}.Add(ops)
return op, layout.Dimens{Size: d, Baseline: d.Y} return layout.Dimens{Size: d, Baseline: d.Y}
} }