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 {
a.w.Profiling = *stats
ops := new(ui.Ops)
for a.w.IsAlive() {
select {
case users := <-a.updateUsers:
@@ -194,23 +195,23 @@ func (a *App) run() error {
}
}
case app.Draw:
ops.Reset()
a.cfg = e.Config
a.faces.Cfg = a.cfg
cs := layout.ExactConstraints(a.w.Size())
root, _ := a.Layout(cs)
a.Layout(ops, cs)
if a.w.Profiling {
op, _ := layout.Align(
layout.Align(
layout.NE,
layout.Margin(a.cfg,
layout.Margins{Top: ui.Dp(16)},
text.Label{Src: textColor, Face: a.face(fonts.mono, 8), Text: a.w.Timings()},
),
).Layout(cs)
root = ui.Ops{root, op}
).Layout(ops, cs)
}
a.w.Draw(root)
a.w.SetTextInput(a.kqueue.Frame(root))
a.pqueue.Frame(root)
a.w.Draw(ops)
a.w.SetTextInput(a.kqueue.Frame(ops))
a.pqueue.Frame(ops)
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))
}
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 {
return a.layoutUsers(cs)
return a.layoutUsers(ops, cs)
} else {
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)
}
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
var ops ui.Ops
if l.Dragging() {
ops = append(ops, key.OpHideInput{})
key.OpHideInput{}.Add(ops)
}
select {
case commits := <-up.commitsResult:
up.commits = commits
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))
}
op, dims := l.Layout()
return append(ops, op), dims
dims := l.Layout()
return dims
}
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}
return layout.Margin(c,
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}).
Init(cs).
Init(ops, cs).
Rigid(avatar).
Flexible(-1, 1, layout.Fit, layout.Margin(c, layout.Margins{Left: ui.Dp(8)}, label)).
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
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(
layout.SE,
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.edit2.Update(c, a.pqueue, a.kqueue)
return st.Expand(0, layout.F(func(cs layout.Constraints) (ui.Op, layout.Dimens) {
return (&layout.Flex{Axis: layout.Vertical, MainAxisAlignment: layout.Start, CrossAxisAlignment: layout.Stretch}).Init(cs).
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(ops, cs).
Rigid(layout.Margin(c,
layout.EqualMargins(ui.Dp(16)),
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)},
a.edit2,
)).
Rigid(layout.F(func(cs layout.Constraints) (ui.Op, layout.Dimens) {
return (&layout.Stack{Alignment: layout.Center}).Init(cs).
Rigid(layout.F(func(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
return (&layout.Stack{Alignment: layout.Center}).Init(ops, cs).
Rigid(layout.Margin(c,
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"},
@@ -490,35 +490,34 @@ func (a *ActionButton) Update(c *ui.Config, q pointer.Events) {
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
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
fl.Rigid(layout.Margin(c,
layout.Margins{Top: ui.Dp(4)},
layout.F(func(cs layout.Constraints) (ui.Op, layout.Dimens) {
op, dims := fab(c, a.sendIco.image(c), fabCol, ui.Dp(56)).Layout(cs)
ops := ui.Ops{op, a.btnClicker.Op(&gesture.Ellipse{dims.Size})}
return ops, dims
layout.F(func(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
dims := fab(c, a.sendIco.image(c), fabCol, ui.Dp(56)).Layout(ops, cs)
a.btnClicker.Op(ops, &gesture.Ellipse{dims.Size})
return dims
}),
))
return fl.Layout()
}
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
l := a.usersList
l.Scroll(c, a.pqueue)
var ops ui.Ops
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))
}
op, dims := l.Layout()
return append(ops, op), dims
dims := l.Layout()
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()}))
return layout.F(func(cs layout.Constraints) (ui.Op, layout.Dimens) {
elem := (&layout.Flex{Axis: layout.Vertical, MainAxisAlignment: layout.Start, CrossAxisAlignment: layout.Start}).Init(cs)
elem.Rigid(layout.F(func(cs layout.Constraints) (ui.Op, layout.Dimens) {
op, dims := layout.Margin(c,
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(ops, cs)
elem.Rigid(layout.F(func(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
dims := layout.Margin(c,
layout.EqualMargins(ui.Dp(8)),
layout.F(func(cs layout.Constraints) (ui.Op, layout.Dimens) {
return centerRowOpts().Init(cs).
layout.F(func(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
return centerRowOpts().Init(ops, cs).
Rigid(layout.Margin(c, layout.Margins{Right: ui.Dp(8)}, avatar)).
Rigid(column(
baseline(
@@ -555,9 +554,9 @@ func (a *App) user(c *ui.Config, index int) layout.Widget {
)).
Layout()
}),
).Layout(cs)
ops := ui.Ops{op, click.Op(&gesture.Rect{dims.Size})}
return ops, dims
).Layout(ops, cs)
click.Op(ops, &gesture.Rect{dims.Size})
return dims
}))
return elem.Layout()
})
@@ -588,8 +587,8 @@ func baseline(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) {
f.Init(cs)
return layout.F(func(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
f.Init(ops, cs)
for _, w := range widgets {
f.Rigid(w)
}
@@ -598,41 +597,39 @@ func flex(f *layout.Flex, widgets ...layout.Widget) layout.Widget {
}
func clipCircle(w layout.Widget) layout.Widget {
return layout.F(func(cs layout.Constraints) (ui.Op, layout.Dimens) {
op, dims := w.Layout(cs)
return layout.F(func(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
ops.Begin()
dims := w.Layout(ops, cs)
block := ops.End()
max := dims.Size.X
if dy := dims.Size.Y; dy > max {
max = dy
}
szf := float32(max)
rr := szf * .5
op = gdraw.OpClip{
Path: rrect(szf, szf, rr, rr, rr, rr),
Op: op,
}
return op, dims
ops.Begin()
gdraw.OpClip{Path: rrect(szf, szf, rr, rr, rr, rr)}.Add(ops)
block.Add(ops)
ops.End().Add(ops)
return dims
})
}
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)
sz := int(szf + .5)
rr := szf * .5
dp := image.Point{X: (sz - ico.Bounds().Dx()) / 2, Y: (sz - ico.Bounds().Dy()) / 2}
dims := image.Point{X: sz, Y: sz}
op := gdraw.OpClip{
Path: rrect(szf, szf, rr, rr, rr, rr),
Op: ui.Ops{
gdraw.OpImage{Rect: f32.Rectangle{Max: f32.Point{X: float32(sz), Y: float32(sz)}}, Src: col, SrcRect: col.Bounds()},
gdraw.OpImage{
Rect: toRectF(ico.Bounds().Add(dp)),
Src: ico,
SrcRect: ico.Bounds(),
},
},
}
return op, layout.Dimens{Size: dims}
gdraw.OpClip{Path: rrect(szf, szf, rr, rr, rr, rr)}.Add(ops)
gdraw.OpImage{Rect: f32.Rectangle{Max: f32.Point{X: float32(sz), Y: float32(sz)}}, Src: col, SrcRect: col.Bounds()}.Add(ops)
gdraw.OpImage{
Rect: toRectF(ico.Bounds().Add(dp)),
Src: ico,
SrcRect: ico.Bounds(),
}.Add(ops)
return 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}}
face := faces.For(regular, ui.Sp(72))
message := "Hello, Gio"
ops := new(ui.Ops)
for w.IsAlive() {
e := <-w.Events()
switch e := e.(type) {
case app.Draw:
faces.Cfg = e.Config
cs := layout.ExactConstraints(w.Size())
root, _ := (text.Label{Src: maroon, Face: face, Alignment: text.Center, Text: message}).Layout(cs)
w.Draw(root)
ops.Reset()
(text.Label{Src: maroon, Face: face, Alignment: text.Center, Text: message}).Layout(ops, cs)
w.Draw(ops)
faces.Frame()
}
}
+101 -75
View File
@@ -14,6 +14,7 @@ import (
"gioui.org/ui/app/internal/gl"
gdraw "gioui.org/ui/draw"
"gioui.org/ui/f32"
"gioui.org/ui/internal/ops"
"gioui.org/ui/internal/path"
"golang.org/x/image/draw"
)
@@ -31,13 +32,13 @@ type GPU struct {
refreshErr chan error
stop chan struct{}
stopped chan struct{}
ops ops
ops drawOps
}
type frame struct {
collectStats bool
viewport image.Point
ops ops
ops drawOps
}
type frameResult struct {
@@ -54,7 +55,8 @@ type renderer struct {
intersections packer
}
type ops struct {
type drawOps struct {
reader ops.Reader
cache *resourceCache
viewport image.Point
clearColor [3]float32
@@ -66,6 +68,14 @@ type ops struct {
pathOps []*pathOp
}
type drawState struct {
clip f32.Rectangle
t ui.Transform
cpath *pathOp
rect bool
z int
}
type pathOp struct {
off f32.Point
// clip is the union of all
@@ -298,12 +308,13 @@ func (g *GPU) Refresh() {
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 {
return
}
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.drawing = true
}
@@ -629,84 +640,99 @@ func floor(v float32) int {
}
}
func (ops *ops) collect(cache *resourceCache, op ui.Op, viewport image.Point) {
ops.clearColor = [3]float32{1.0, 1.0, 1.0}
ops.cache = cache
ops.viewport = viewport
ops.imageOps = ops.imageOps[:0]
ops.zimageOps = ops.zimageOps[:0]
ops.pathOps = ops.pathOps[:0]
func (d *drawOps) reset(cache *resourceCache, viewport image.Point) {
d.clearColor = [3]float32{1.0, 1.0, 1.0}
d.cache = cache
d.viewport = viewport
d.imageOps = d.imageOps[:0]
d.zimageOps = d.zimageOps[: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{
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 {
type childOp interface {
ChildOp() ui.Op
}
switch op := op.(type) {
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() {
func (d *drawOps) collectOps(r *ops.Reader, clip f32.Rectangle, t ui.Transform, cpath *pathOp, rect bool, z int) int {
loop:
for {
data, ok := r.Decode()
if !ok {
break
}
cpath := &pathOp{
parent: cpath,
off: off,
switch ops.OpType(data[0]) {
case ops.TypeTransform:
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
}
+27 -28
View File
@@ -11,6 +11,7 @@ import (
"gioui.org/ui"
"gioui.org/ui/app/internal/gpu"
"gioui.org/ui/internal/ops"
"gioui.org/ui/key"
"gioui.org/ui/pointer"
)
@@ -41,6 +42,8 @@ type Window struct {
hasNextFrame bool
nextFrame time.Time
delayedDraw *time.Timer
reader ops.Reader
}
// driver is the interface for the platform implementation
@@ -89,7 +92,7 @@ func (w *Window) Err() error {
return w.err
}
func (w *Window) Draw(root ui.Op) {
func (w *Window) Draw(root *ui.Ops) {
if !w.IsAlive() {
return
}
@@ -132,13 +135,35 @@ func (w *Window) Draw(root ui.Op) {
w.timings = fmt.Sprintf("t:%7s %s", frameDur, w.gpu.Timings())
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.updateAnimation()
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() {
if !w.IsAlive() {
return
@@ -256,29 +281,3 @@ func (w *Window) event(e Event) {
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
import (
"encoding/binary"
"image"
"math"
"gioui.org/ui"
"gioui.org/ui/f32"
"gioui.org/ui/internal/ops"
"gioui.org/ui/internal/path"
)
@@ -17,18 +19,65 @@ type OpImage struct {
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() {}
// ClipRect returns a special case of OpClip
// that clips to a pixel aligned rectangular area.
func ClipRect(r image.Rectangle, op ui.Op) OpClip {
return OpClip{
Path: &Path{
data: &path.Path{
Bounds: toRectF(r),
},
// RectPath constructs a path corresponding to
// a pixel aligned rectangular area.
func RectPath(r image.Rectangle) *Path {
return &Path{
data: &path.Path{
Bounds: toRectF(r),
},
Op: op,
}
}
+21 -4
View File
@@ -3,16 +3,17 @@
package draw
import (
"encoding/binary"
"math"
"gioui.org/ui"
"gioui.org/ui/f32"
"gioui.org/ui/internal/ops"
"gioui.org/ui/internal/path"
)
type OpClip struct {
Path *Path
Op ui.Op
}
type Path struct {
@@ -33,11 +34,25 @@ func (p *Path) Data() interface{} {
return p.data
}
func (p OpClip) ChildOp() ui.Op {
return p.Op
func (c OpClip) Add(o *ui.Ops) {
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.
func (p *PathBuilder) Move(to f32.Point) {
@@ -265,3 +280,5 @@ func (p *PathBuilder) Path() *Path {
}
return data
}
func (p OpClip) ImplementsOp() {}
+7 -6
View File
@@ -83,8 +83,9 @@ const (
thresholdVelocity = 1
)
func (c *Click) Op(a pointer.Area) pointer.OpHandler {
return pointer.OpHandler{Area: a, Key: c}
func (c *Click) Op(ops *ui.Ops, a pointer.Area) {
op := pointer.OpHandler{Area: a, Key: c}
op.Add(ops)
}
func (c *Click) Update(q pointer.Events) []ClickEvent {
@@ -115,12 +116,12 @@ func (c *Click) Update(q pointer.Events) []ClickEvent {
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}
if !s.flinger.Active() {
return oph
oph.Add(ops)
if s.flinger.Active() {
ui.OpRedraw{}.Add(ops)
}
return ui.Ops{oph, ui.OpRedraw{}}
}
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
import (
"encoding/binary"
"gioui.org/ui"
"gioui.org/ui/internal/ops"
)
type OpHandler struct {
Key Key
Focus bool
@@ -63,6 +70,35 @@ const (
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 (OpHideInput) ImplementsOp() {}
+35 -28
View File
@@ -4,12 +4,14 @@ package key
import (
"gioui.org/ui"
"gioui.org/ui/internal/ops"
)
type Queue struct {
focus Key
events []Event
handlers map[Key]bool
reader ops.Reader
}
type listenerPriority uint8
@@ -21,9 +23,10 @@ const (
priNewFocus
)
func (q *Queue) Frame(op ui.Op) TextInputState {
func (q *Queue) Frame(root *ui.Ops) TextInputState {
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
for k, active := range q.handlers {
if !active || changed {
@@ -71,39 +74,43 @@ func (q *Queue) For(k Key) []Event {
return q.events
}
func resolveFocus(op ui.Op, focus Key) (Key, listenerPriority, bool) {
type childOp interface {
ChildOp() ui.Op
}
func resolveFocus(r *ops.Reader, focus Key) (Key, listenerPriority, bool) {
var k Key
var pri listenerPriority
var hide bool
switch op := op.(type) {
case ui.Ops:
for i := len(op) - 1; i >= 0; i-- {
newK, newPri, h := resolveFocus(op[i], focus)
loop:
for {
data, ok := r.Decode()
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
if newPri > pri {
if newPri >= pri {
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
}
+29 -23
View File
@@ -15,20 +15,20 @@ type Flex struct {
CrossAxisAlignment CrossAxisAlignment
MainAxisSize MainAxisSize
cs Constraints
ops *ui.Ops
cs Constraints
children []flexChild
taken int
maxCross int
maxBaseline int
ccache [10]flexChild
opCache [10]ui.Op
ccache [10]flexChild
}
type flexChild struct {
op ui.Op
dims Dimens
block ui.OpBlock
dims Dimens
}
type MainAxisSize uint8
@@ -60,7 +60,8 @@ const (
Stretch
)
func (f *Flex) Init(cs Constraints) *Flex {
func (f *Flex) Init(ops *ui.Ops, cs Constraints) *Flex {
f.ops = ops
f.cs = cs
if f.children == nil {
f.children = f.ccache[:0]
@@ -78,7 +79,10 @@ func (f *Flex) Rigid(w Widget) *Flex {
mainMax -= f.taken
}
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)
if c := axisCross(f.Axis, dims.Size); c > f.maxCross {
f.maxCross = c
@@ -86,7 +90,7 @@ func (f *Flex) Rigid(w Widget) *Flex {
if b := dims.Baseline; b > f.maxBaseline {
f.maxBaseline = b
}
f.children = append(f.children, flexChild{op, dims})
f.children = append(f.children, flexChild{block, dims})
return f
}
@@ -101,7 +105,10 @@ func (f *Flex) Flexible(idx int, flex float32, mode FlexMode, w Widget) *Flex {
submainc.Min = submainc.Max
}
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)
if c := axisCross(f.Axis, dims.Size); c > f.maxCross {
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 {
f.maxBaseline = b
}
f.children = append(f.children, flexChild{op, dims})
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
}
func (f *Flex) Layout() (ui.Op, Dimens) {
func (f *Flex) Layout() Dimens {
mainc := axisMainConstraint(f.Axis, f.cs)
crossSize := axisCrossConstraint(f.Axis, f.cs).Constrain(f.maxCross)
var space int
@@ -140,13 +148,7 @@ func (f *Flex) Layout() (ui.Op, Dimens) {
case SpaceAround:
mainSize += space / (len(f.children) * 2)
}
var ops ui.Ops
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 {
for _, child := range f.children {
dims := child.dims
b := dims.Baseline
var cross int
@@ -160,8 +162,12 @@ func (f *Flex) Layout() (ui.Op, Dimens) {
cross = f.maxBaseline - b
}
}
off := ui.Offset(toPointF(axisPoint(f.Axis, mainSize, cross)))
ops[i] = ui.OpLayer{Op: ui.OpTransform{Transform: off, Op: child.op}}
f.ops.Begin()
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)
switch f.MainAxisAlignment {
case SpaceEvenly:
@@ -187,7 +193,7 @@ func (f *Flex) Layout() (ui.Op, Dimens) {
if baseline == 0 {
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 {
+36 -24
View File
@@ -12,8 +12,8 @@ import (
)
type scrollChild struct {
op ui.Op
size image.Point
size image.Point
block ui.OpBlock
}
type List struct {
@@ -24,12 +24,14 @@ type List struct {
// The distance scrolled since last call to Init.
Distance int
area gesture.Rect
scroll gesture.Scroll
scrollDir int
offset int
first int
ops *ui.Ops
cs Constraints
len int
@@ -38,7 +40,6 @@ type List struct {
elem func(w Widget)
size image.Point
ops ui.Ops
}
type Interface interface {
@@ -46,19 +47,20 @@ type Interface interface {
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.children = l.children[:0]
l.ops = ops
l.cs = cs
l.len = len
l.elem = nil
if l.first > len {
l.first = len
}
l.ops = l.ops[:0]
if len == 0 {
return 0, false
}
l.scroll.Op(ops, &l.area)
return l.Index()
}
@@ -82,9 +84,9 @@ func (l *List) Index() (int, bool) {
return i, ok
}
func (l *List) Layout() (ui.Op, Dimens) {
ops := append(ui.Ops{l.scroll.Op(&gesture.Rect{l.size})}, l.ops...)
return ops, Dimens{Size: l.size}
func (l *List) Layout() Dimens {
l.area.Size = l.size
return Dimens{Size: l.size}
}
func (l *List) next() (int, bool) {
@@ -116,21 +118,28 @@ func (l *List) Elem(w Widget) {
}
func (l *List) backward(w Widget) {
subcs := axisConstraints(l.Axis, Constraint{Max: ui.Inf}, l.crossConstraintChild(l.cs))
l.first--
op, dims := w.Layout(subcs)
mainSize := axisMain(l.Axis, dims.Size)
child := l.add(w)
mainSize := axisMain(l.Axis, child.size)
l.offset += 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) {
subcs := axisConstraints(l.Axis, Constraint{Max: ui.Inf}, l.crossConstraintChild(l.cs))
op, dims := w.Layout(subcs)
mainSize := axisMain(l.Axis, dims.Size)
child := l.add(w)
mainSize := axisMain(l.Axis, child.size)
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() {
@@ -176,14 +185,17 @@ func (l *List) draw() {
if min < 0 {
min = 0
}
op := draw.ClipRect(
image.Rectangle{
Min: axisPoint(l.Axis, min, -ui.Inf),
Max: axisPoint(l.Axis, max, ui.Inf),
},
ui.OpTransform{Transform: ui.Offset(toPointF(axisPoint(l.Axis, pos, cross))), Op: child.op},
)
l.ops = append(l.ops, ui.OpLayer{Op: op})
r := image.Rectangle{
Min: axisPoint(l.Axis, min, -ui.Inf),
Max: axisPoint(l.Axis, max, ui.Inf),
}
l.ops.Begin()
draw.OpClip{Path: draw.RectPath(r)}.Add(l.ops)
ui.OpTransform{
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)
}
atStart := l.first == 0 && l.offset <= 0
+25 -19
View File
@@ -10,7 +10,7 @@ import (
)
type Widget interface {
Layout(cs Constraints) (ui.Op, Dimens)
Layout(ops *ui.Ops, cs Constraints) Dimens
}
type Constraints struct {
@@ -29,7 +29,7 @@ type Dimens struct {
type Axis uint8
type F func(cs Constraints) (ui.Op, Dimens)
type F func(ops *ui.Ops, cs Constraints) Dimens
const (
Horizontal Axis = iota
@@ -73,8 +73,8 @@ func ExactConstraints(size image.Point) Constraints {
}
}
func (f F) Layout(cs Constraints) (ui.Op, Dimens) {
return f(cs)
func (f F) Layout(ops *ui.Ops, cs Constraints) Dimens {
return f(ops, cs)
}
type Margins struct {
@@ -82,7 +82,7 @@ type Margins struct {
}
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
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 {
@@ -105,10 +105,11 @@ func Margin(c *ui.Config, m Margins, w Widget) Widget {
mcs.Height.Max = mcs.Height.Min
}
}
op, dims := w.Layout(mcs)
op = ui.OpTransform{Transform: ui.Offset(toPointF(image.Point{X: l, Y: t})), Op: op}
return op, Dimens{
ops.Begin()
ui.OpTransform{Transform: ui.Offset(toPointF(image.Point{X: l, Y: t}))}.Add(ops)
dims := w.Layout(ops, mcs)
ops.End().Add(ops)
return Dimens{
Size: cs.Constrain(dims.Size.Add(image.Point{X: r + l, Y: t + b})),
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 {
return F(func(cs Constraints) (ui.Op, Dimens) {
return F(func(ops *ui.Ops, cs Constraints) Dimens {
if !isInf(maxWidth) {
mw := int(c.Pixels(maxWidth) + .5)
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
}
}
return wt.Layout(cs)
return wt.Layout(ops, cs)
})
}
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 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
}
}
return wt.Layout(cs)
return wt.Layout(ops, cs)
})
}
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 {
cs.Height.Min = cs.Height.Max
}
if cs.Width.Max != ui.Inf {
cs.Width.Min = cs.Width.Max
}
return w.Layout(cs)
return w.Layout(ops, cs)
})
}
func Align(alignment Direction, w Widget) Widget {
return F(func(cs Constraints) (ui.Op, Dimens) {
op, dims := w.Layout(cs.Loose())
return F(func(ops *ui.Ops, cs Constraints) Dimens {
ops.Begin()
dims := w.Layout(ops, cs.Loose())
block := ops.End()
sz := dims.Size
if cs.Width.Max != ui.Inf {
sz.X = cs.Width.Max
@@ -204,8 +207,11 @@ func Align(alignment Direction, w Widget) Widget {
case SW, S, SE:
p.Y = sz.Y - dims.Size.Y
}
op = ui.OpTransform{Transform: ui.Offset(toPointF(p)), Op: op}
return op, Dimens{
ops.Begin()
ui.OpTransform{Transform: ui.Offset(toPointF(p))}.Add(ops)
block.Add(ops)
ops.End().Add(ops)
return Dimens{
Size: sz,
Baseline: dims.Baseline,
}
+29 -22
View File
@@ -11,18 +11,18 @@ import (
type Stack struct {
Alignment Direction
ops *ui.Ops
cs Constraints
children []stackChild
maxSZ image.Point
baseline int
ccache [10]stackChild
opCache [10]ui.Op
ccache [10]stackChild
}
type stackChild struct {
op ui.Op
dims Dimens
block ui.OpBlock
dims Dimens
}
type Direction uint8
@@ -38,26 +38,31 @@ const (
W
)
func (s *Stack) Init(cs Constraints) *Stack {
func (s *Stack) Init(ops *ui.Ops, cs Constraints) *Stack {
if s.children == nil {
s.children = s.ccache[:0]
}
s.children = s.children[:0]
s.maxSZ = image.Point{}
s.baseline = 0
s.ops = ops
s.cs = cs
return s
}
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 {
s.maxSZ.X = w
}
if h := dims.Size.Y; h > s.maxSZ.Y {
s.maxSZ.Y = h
}
s.add(op, dims)
s.addjustBaseline(dims)
s.children = append(s.children, stackChild{b, dims})
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},
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 {
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
}
func (s *Stack) add(op ui.Op, dims Dimens) {
s.children = append(s.children, stackChild{op, dims})
func (s *Stack) addjustBaseline(dims Dimens) {
if s.baseline == 0 {
if b := dims.Baseline; b != dims.Size.Y {
s.baseline = b
@@ -83,14 +93,8 @@ func (s *Stack) add(op ui.Op, dims Dimens) {
}
}
func (s *Stack) Layout() (ui.Op, Dimens) {
var ops ui.Ops
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 {
func (s *Stack) Layout() Dimens {
for _, ch := range s.children {
sz := ch.dims.Size
var p image.Point
switch s.Alignment {
@@ -105,13 +109,16 @@ func (s *Stack) Layout() (ui.Op, Dimens) {
case SW, S, SE:
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
if b == 0 {
b = s.maxSZ.Y
}
return ops, Dimens{
return Dimens{
Size: s.maxSZ,
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
import (
"encoding/binary"
"time"
"gioui.org/ui"
"gioui.org/ui/f32"
"gioui.org/ui/internal/ops"
)
type Event struct {
@@ -66,6 +69,32 @@ const (
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 {
switch t {
case Press:
+34 -43
View File
@@ -5,12 +5,14 @@ package pointer
import (
"gioui.org/ui"
"gioui.org/ui/f32"
"gioui.org/ui/internal/ops"
)
type Queue struct {
hitTree []hitNode
handlers map[Key]*handler
pointers []pointerInfo
reader ops.Reader
scratch []Key
}
@@ -35,49 +37,37 @@ type handler struct {
wantsGrab bool
}
type childOp interface {
ChildOp() ui.Op
}
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]
func (q *Queue) collectHandlers(r *ops.Reader, t ui.Transform, layer int) {
for {
data, ok := r.Decode()
if !ok {
h = new(handler)
q.handlers[op.Key] = h
return
}
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()
for k, h := range q.handlers {
if !h.active {
@@ -126,7 +116,8 @@ func (q *Queue) Frame(op ui.Op) {
}
}
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 {
+14 -13
View File
@@ -48,8 +48,6 @@ type Editor struct {
scrollOff image.Point
clicker gesture.Click
ops ui.Ops
}
type linePath struct {
@@ -126,7 +124,7 @@ func (e *Editor) caretWidth() fixed.Int26_6 {
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)
e.padLeft, e.padRight = twoDp, twoDp
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},
Max: image.Point{X: e.viewSize.X, Y: e.viewSize.Y},
}
e.ops = e.ops[:0]
e.ops = append(e.ops, key.OpHandler{Key: e, Focus: e.requestFocus})
key.OpHandler{Key: e, Focus: e.requestFocus}.Add(ops)
e.requestFocus = false
e.it = lineIterator{
Lines: lines,
@@ -171,10 +168,11 @@ func (e *Editor) Layout(cs layout.Constraints) (ui.Op, layout.Dimens) {
break
}
path := e.Face.Path(str)
e.ops = append(e.ops, ui.OpTransform{
Transform: ui.Offset(lineOff),
Op: draw.OpClip{Path: path, Op: draw.OpImage{Rect: toRectF(clip).Sub(lineOff), Src: e.Src, SrcRect: e.Src.Bounds()}},
})
ops.Begin()
ui.OpTransform{Transform: ui.Offset(lineOff)}.Add(ops)
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 {
now := e.cfg.Now
@@ -197,18 +195,21 @@ func (e *Editor) Layout(cs layout.Constraints) (ui.Op, layout.Dimens) {
})
carRect = clip.Intersect(carRect)
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 {
e.ops = append(e.ops, ui.OpRedraw{At: nextBlink})
redraw := ui.OpRedraw{At: nextBlink}
redraw.Add(ops)
}
}
baseline := e.padTop + e.dims.Baseline
area := &gesture.Rect{e.viewSize}
e.ops = append(e.ops, e.scroller.Op(area), e.clicker.Op(area))
return e.ops, layout.Dimens{Size: e.viewSize, Baseline: baseline}
e.scroller.Op(ops, area)
e.clicker.Op(ops, area)
return layout.Dimens{Size: e.viewSize, Baseline: baseline}
}
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
}
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)
lines := textLayout.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},
Max: image.Point{X: ui.Inf, Y: dims.Size.Y + padBottom},
}
var ops ui.Ops = make([]ui.Op, len(lines))[:0]
l.it = lineIterator{
Lines: lines,
Clip: clip,
@@ -104,13 +103,13 @@ func (l Label) Layout(cs layout.Constraints) (ui.Op, layout.Dimens) {
}
path := l.Face.Path(str)
lclip := toRectF(clip).Sub(off)
op := ui.OpTransform{
Transform: ui.Offset(off),
Op: draw.OpClip{Path: path, Op: draw.OpImage{Rect: lclip, Src: l.Src, SrcRect: l.Src.Bounds()}},
}
ops = append(ops, op)
ops.Begin()
ui.OpTransform{Transform: ui.Offset(off)}.Add(ops)
draw.OpClip{Path: path}.Add(ops)
draw.OpImage{Rect: lclip, Src: l.Src, SrcRect: l.Src.Bounds()}.Add(ops)
ops.End().Add(ops)
}
return ops, dims
return dims
}
func itof(i int) float32 {
+59 -11
View File
@@ -3,9 +3,12 @@
package ui
import (
"encoding/binary"
"math"
"time"
"gioui.org/ui/f32"
"gioui.org/ui/internal/ops"
)
// 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.
type Op interface {
ImplementsOp()
@@ -41,7 +44,6 @@ type Op interface {
// OpLayer represents a semantic layer of UI.
type OpLayer struct {
Op Op
}
// OpRedraw requests a redraw at the given time. Use
@@ -50,13 +52,9 @@ type OpRedraw struct {
At time.Time
}
// Ops is the operation for a list of ops.
type Ops []Op
// OpTransform transforms an op.
type OpTransform struct {
Transform Transform
Op Op
}
type Transform struct {
@@ -64,6 +62,30 @@ type Transform struct {
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 {
return p.Sub(t.offset)
}
@@ -78,12 +100,39 @@ func (t Transform) Mul(t2 Transform) Transform {
}
}
func (t OpTransform) ChildOp() Op {
return t.Op
func (t OpTransform) Add(o *Ops) {
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 {
return o.Op
func (t *OpTransform) Decode(d []byte) {
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 {
@@ -93,7 +142,6 @@ func Offset(o f32.Point) Transform {
// Inf is the int value that represents an unbounded maximum constraint.
const Inf = int(^uint(0) >> 1)
func (Ops) ImplementsOp() {}
func (OpLayer) ImplementsOp() {}
func (OpTransform) ImplementsOp() {}
func (OpRedraw) ImplementsOp() {}
+3 -3
View File
@@ -16,7 +16,7 @@ type Image struct {
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}
if d.X == ui.Inf {
d.X = cs.Width.Min
@@ -27,6 +27,6 @@ func (im Image) Layout(cs layout.Constraints) (ui.Op, layout.Dimens) {
dr := f32.Rectangle{
Max: f32.Point{X: float32(d.X), Y: float32(d.Y)},
}
op := draw.OpImage{Rect: dr, Src: im.Src, SrcRect: im.Rect}
return op, layout.Dimens{Size: d, Baseline: d.Y}
draw.OpImage{Rect: dr, Src: im.Src, SrcRect: im.Rect}.Add(ops)
return layout.Dimens{Size: d, Baseline: d.Y}
}