mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
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:
+64
-67
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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() {}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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() {}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user